Intermediate Python 📂 Class and Object · 5 of 10 214 min read

Python OOP — Private Methods & Private Variables

Learn how Python handles encapsulation without a real private keyword. Master the single underscore convention (_protected), the double underscore name-mangling trick (__private), private helper methods, and how they combine to give you a safe public API with a hidden implementation. Includes a shared-house analogy, three animated SVG diagrams (access levels, name mangling, private helper flow), a full BankAccount PIN example, and 10 golden rules.

🌐
SectionSection第 01 节

The Story That Explains Private Members 🏠Private Members समझाने वाली कहानी 🏠一个讲清私有成员的故事 🏠

The Shared House — Bulletin Board, Bedroom, Locked Safeसाझा घर — Notice Board, Bedroom, बंद तिजोरी合租房 — 公告板、卧室、上锁的保险箱
Imagine you live in a shared house with three flatmates. Three kinds of things live in this house.

The bulletin board in the hallway — notes about the rubbish schedule, the WiFi password, the address. Anyone can read them and update them. These are public attributes.

Your bedroom door with a "please knock" sign — the door is not locked. Your flatmates could walk in, but by convention they don't. If they enter anyway, it is their responsibility if something breaks. These are protected attributes (one underscore).

Your locked safe inside the bedroom — passport, bank cards, diary. The house rules actively rename the safe so nobody finds it by accident. You can technically still open it, but you have clearly gone out of your way. These are private attributes (two underscores, name-mangled).

Python has no hard private keyword. It uses naming conventions and one small piece of magic (name mangling) to show intent: "public — help yourself", "protected — don't unless you know what you're doing", and "private — really, this is not for you".
कल्पना करें कि आप तीन साथियों के साथ एक साझा घर में रहते हैं। इस घर में तीन तरह की चीज़ें हैं।

गलियारे का notice board — कूड़े का schedule, WiFi password, पता। कोई भी इन्हें पढ़ और बदल सकता है। ये public attributes हैं।

"कृपया दस्तक दें" लिखा आपका bedroom का दरवाज़ा — दरवाज़ा बंद नहीं है। साथी अंदर आ सकते हैं, पर परंपरा से नहीं आते। फिर भी आएँ, तो कुछ टूटने की ज़िम्मेदारी उनकी। ये protected attributes हैं (एक underscore)।

Bedroom के अंदर आपकी बंद तिजोरी — passport, bank cards, diary। घर के नियम तिजोरी का नाम ही बदल देते हैं ताकि कोई ग़लती से न पाए। तकनीकी रूप से आप फिर भी उसे खोल सकते हैं, पर साफ़ है कि आपने ख़ास कोशिश की। ये private attributes हैं (दो underscores, name-mangled)।

Python में कोई सख़्त private keyword नहीं है। वह इरादा दिखाने के लिए naming conventions और एक छोटा-सा जादू (name mangling) इस्तेमाल करता है: "public — ले लीजिए", "protected — जब तक पता न हो, न छुएँ", और "private — सच में, यह आपके लिए नहीं"।
想象你和三位室友合租一套房子。房子里有三类东西。

走廊的公告板 — 倒垃圾时间表、WiFi 密码、地址。谁都可以看,也可以改。这些是 公有属性。

挂着"请敲门"牌子的卧室门 — 门没锁。室友 可以 进来,但按约定他们不会。硬要进来,弄坏了东西就是他们的责任。这些是 受保护属性(一个下划线)。

卧室里上锁的保险箱 — 护照、银行卡、日记。房规会主动给保险箱改个名,让人不会无意中找到。技术上 你仍然能打开它,但显然是特意为之。这些是 私有属性(两个下划线,名字改编)。

Python 没有严格的 private 关键字。它用 命名约定 和一点小魔法(名字改编)来表达意图:"公有 — 随便用","受保护 — 不懂就别碰","私有 — 真的,这不是给你的"。
🔑
The Core Insightमूल बात核心要点

Python does not enforce privacy the way Java or C++ does — it signals it. One underscore _x is a polite request. Two underscores __x trigger name mangling, which renames the attribute inside the class so accidental access becomes very unlikely. Together they let you build classes with a clear, safe public API and an inside that outsiders cannot casually touch.

Python privacy को Java या C++ की तरह लागू नहीं करता — वह उसका संकेत देता है। एक underscore _x एक विनम्र अनुरोध है। दो underscores __x name mangling चालू करते हैं, जो class के अंदर attribute का नाम बदल देता है ताकि ग़लती से पहुँचना लगभग नामुमकिन हो जाए। दोनों मिलकर आपको साफ़, सुरक्षित public API वाली classes बनाने देते हैं, जिनके अंदरूनी हिस्से को बाहर वाले यूँ ही नहीं छू सकते।

Python 不像 Java 或 C++ 那样 强制 私有 — 它只是 发出信号。一个下划线 _x 是礼貌的请求。两个下划线 __x 会触发 名字改编(name mangling),在类内部给属性改名,让无意的访问几乎不可能。两者结合,让你写出公有 API 清晰安全、内部不被外人随手触碰的类。

▶️
This Tutorial Has Five Simulators and a Quizइस Tutorial में पाँच Simulators और एक Quiz है本教程有五个模拟器和一个小测验

Look for the interactive boxes: the Access Tester (Section 05), the Name Mangler (06), Private Helpers Step by Step (07), the Bank Vault (08) and the Bug Doctor (10). Every result comes from real Python runs.

Interactive डिब्बे खोजें: Access Tester (Section 05), Name Mangler (06), Private Helpers क़दम-दर-क़दम (07), Bank Vault (08) और Bug Doctor (10)। हर नतीजा असली Python runs से है।

请留意这些互动框:访问测试器(第 05 节)、名字改编器(06)、私有辅助方法逐步演示(07)、银行金库(08)和 Bug 医生(10)。每个结果都来自真实的 Python 运行。


SectionSection第 02 节

The Problem — Python Makes Everything Public by Default 🚪समस्या — Python में डिफ़ॉल्ट रूप से सब Public है 🚪问题 — Python 默认一切都是公有的 🚪

When you write self.balance = 1000 in __init__, any caller can reach into the object and set acc.balance = -99999999. Nothing stops them. That is fine for a quick script, but for a real class it is a disaster.

जब आप __init__ में self.balance = 1000 लिखते हैं, तो कोई भी caller object में पहुँचकर acc.balance = -99999999 कर सकता है। कुछ नहीं रोकता। छोटी script के लिए ठीक है, पर असली class के लिए यह आफ़त है।

当你在 __init__ 里写 self.balance = 1000,任何调用方都能伸手进对象,设置 acc.balance = -99999999。没有东西能阻止。对小脚本无所谓,对真正的类却是灾难。

class BankAccount:
    def __init__(self, holder, balance):
        self.holder  = holder
        self.balance = balance         # public - anyone can touch it

acc = BankAccount("Alice", 1000)

# Nothing stops this - the whole class contract is gone
acc.balance = -99999999
print(acc.balance)                 # no validation, no history, no protection
OUTPUT
-99999999
⚠️
The Fix Is Not Enforcement — It Is Conventionहल ज़बरदस्ती नहीं — परंपरा है解决办法不是强制 — 而是约定

Python's philosophy is "we are all consenting adults". It gives you tools to signal what is part of your public API and what is not. Callers who ignore those signals do so at their own risk. That is usually enough, because in real teams code review catches anyone who reaches into a class's private parts.

Python का दर्शन है "हम सब समझदार वयस्क हैं"। वह आपको यह संकेत देने के औज़ार देता है कि क्या आपकी public API का हिस्सा है और क्या नहीं। जो callers इन संकेतों को अनदेखा करते हैं, वे अपने जोखिम पर करते हैं। आमतौर पर इतना काफ़ी है, क्योंकि असली teams में code review हर उस व्यक्ति को पकड़ लेता है जो class के private हिस्सों में हाथ डालता है।

Python 的哲学是"我们都是成年人"。它给你工具来 标明 哪些属于公有 API、哪些不属于。无视这些信号的调用方后果自负。这通常就够了,因为在真实团队里,代码审查会抓住任何伸手进类私有部分的人。


SectionSection第 03 节

One Underscore _protected — "Please Knock" 🚪एक Underscore _protected — "कृपया दस्तक दें" 🚪一个下划线 _protected — "请敲门" 🚪

A name that starts with one underscore (self._log, self._retries) tells every reader: "this is internal to the class — don't touch it from outside unless you know what you're doing." Python itself does nothing special: no renaming, no errors. It is a pure convention that the whole community respects.

एक underscore से शुरू होने वाला नाम (self._log, self._retries) हर पढ़ने वाले को बताता है: "यह class का अंदरूनी है — जब तक पता न हो, बाहर से न छुएँ।" Python ख़ुद कुछ ख़ास नहीं करता: न नाम बदलना, न errors। यह शुद्ध परंपरा है जिसे पूरा community मानता है।

以 一个下划线 开头的名字(self._log、self._retries)告诉每位读者:"这是类的内部细节 — 除非你清楚自己在做什么,否则别从外部碰它。"Python 本身不做任何特殊处理:不改名,不报错。这纯粹是整个社区都遵守的约定。

class BankAccount:
    def __init__(self, holder, balance):
        self.holder   = holder         # public - okay to read/write
        self._log     = []             # protected - internal, but subclasses may extend it
        self._version = 1              # protected - internal bookkeeping

acc = BankAccount("Alice", 1000)

# You CAN still touch _log from outside - Python doesn't stop you...
print(acc._log)
print(acc.__dict__)                # no renaming: the names are stored as written

# ...but every Python developer reads "_log" as "internal - don't touch."
OUTPUT
[] {'holder': 'Alice', '_log': [], '_version': 1}
💡
When to Use One Underscoreएक Underscore कब इस्तेमाल करें何时使用单下划线

Use _name for attributes and methods that are internal, but that subclasses may still need. Common examples: _log, _cache, _state, _normalize(). One underscore says "not part of my public API, but not sealed off from my family either".

_name उन attributes और methods के लिए इस्तेमाल करें जो अंदरूनी हैं, पर जिनकी subclasses को फिर भी ज़रूरत हो सकती है। आम उदाहरण: _log, _cache, _state, _normalize()। एक underscore कहता है "मेरी public API का हिस्सा नहीं, पर मेरे परिवार से बंद भी नहीं"।

_name 用于 内部 但 子类可能仍需要 的属性和方法。常见例子:_log、_cache、_state、_normalize()。单下划线的意思是"不属于我的公有 API,但也不对我的家族封闭"。


SectionSection第 04 节

Two Underscores __private — Name Mangling Locks the Safe 🔒दो Underscores __private — Name Mangling तिजोरी बंद करता है 🔒两个下划线 __private — 名字改编锁上保险箱 🔒

A name that starts with two underscores (and does not end with two) triggers Python's name mangling. Inside the class, Python quietly renames self.__balance to self._BankAccount__balance. Anyone who tries the obvious acc.__balance from outside gets an AttributeError.

दो underscores से शुरू होने वाला नाम (जो दो पर ख़त्म न हो) Python की name mangling चालू करता है। Class के अंदर Python चुपचाप self.__balance का नाम self._BankAccount__balance कर देता है। बाहर से सीधा acc.__balance आज़माने वाले को AttributeError मिलता है।

以 两个下划线 开头(且不以两个下划线结尾)的名字会触发 Python 的 名字改编。在类内部,Python 悄悄把 self.__balance 改名为 self._BankAccount__balance。任何人从外部直接尝试 acc.__balance 都会得到 AttributeError。

class BankAccount:
    def __init__(self, holder, balance):
        self.holder    = holder
        self.__balance = balance       # private - Python will rename it
        self.__pin     = None          # private - this must never leak

    def show(self):
        # Inside the class, __balance works as expected
        print(f"{self.holder}: ${self.__balance}")

acc = BankAccount("Alice", 1000)
acc.show()

# -- The obvious access from outside FAILS --
try:
    print(acc.__balance)
except AttributeError as e:
    print("Blocked:", e)

# -- What Python actually did: it renamed the attribute --
print(acc.__dict__)

# -- You COULD still reach it by the mangled name, but everyone knows you are cheating --
print(acc._BankAccount__balance)
OUTPUT
Alice: $1000 Blocked: 'BankAccount' object has no attribute '__balance' {'holder': 'Alice', '_BankAccount__balance': 1000, '_BankAccount__pin': None} 1000
🔒
Name Mangling Is Not Real SecurityName Mangling असली Security नहीं है名字改编不是真正的安全措施

Name mangling stops accidental access — the "oops, I typed acc.__balance" mistake. It does not stop someone determined. Anyone can still write acc._BankAccount__balance. In Python, private means "actively discouraged", not "sealed". Never use it as a security boundary.

Name mangling ग़लती से होने वाली पहुँच रोकता है — "अरे, मैंने acc.__balance लिख दिया" वाली ग़लती। यह ठान चुके व्यक्ति को नहीं रोकता। कोई भी acc._BankAccount__balance लिख सकता है। Python में private का मतलब है "सख़्ती से हतोत्साहित", "सील बंद" नहीं। इसे कभी security की दीवार न मानें।

名字改编只能阻止 无意 的访问 — 那种"哎呀,我打了 acc.__balance"的失误。它 挡不住 有意为之的人。谁都可以写 acc._BankAccount__balance。在 Python 里,私有意味着"强烈不鼓励",而不是"密封"。永远不要把它当作安全边界。


SectionSection第 05 节

Public vs Protected vs Private — Three Access Levels 🚦Public बनाम Protected बनाम Private — तीन Access Levels 🚦公有、受保护与私有 — 三种访问级别 🚦

Three attributes on one BankAccount. Watch what happens when outside code tries each of them: public works at once, protected works with a "you shouldn't" warning, private fails with an AttributeError.

एक BankAccount पर तीन attributes। देखें कि बाहर का code हर एक को आज़माता है तो क्या होता है: public तुरंत चलता है, protected "नहीं करना चाहिए" की चेतावनी के साथ चलता है, private AttributeError से fail होता है।

一个 BankAccount 上有三个属性。看看外部代码逐个尝试时会发生什么:公有立即可用,受保护的能用但带着"不该这样"的警告,私有的直接 AttributeError。

Three Tries, Three Different Outcomesतीन कोशिशें, तीन अलग नतीजे三次尝试,三种结果
Three access levelsacc = BankAccount(...)PUBLICself.holder = "Alice"no prefix — open APIकोई prefix नहीं — खुली API无前缀 — 开放 APIacc.holder→ 'Alice'✓works · public APIचलता है · public API可用 · 公有 APIPROTECTEDself._log = []one underscore — please knockएक underscore — दस्तक दें一个下划线 — 请敲门acc._log→ []⚠works, but discouragedचलता है, पर ठीक नहीं能用,但不鼓励PRIVATEself.__balance = 1000stored as _BankAccount__balance_BankAccount__balance नाम से रखा存为 _BankAccount__balanceacc.__balance→ AttributeError✗name-mangled — blockedname-mangled — रुका名字已改编 — 被拦下TRIES FROM OUTSIDEबाहर से कोशिशें来自外部的尝试

Public is open, protected is only a signal, private is truly harder to reach because Python renamed it.

Public खुला है, protected सिर्फ़ संकेत है, private तक पहुँचना सच में मुश्किल है क्योंकि Python ने उसका नाम बदल दिया।

公有是开放的,受保护只是信号,私有真的更难访问,因为 Python 给它改了名。

🚦 Simulator: Access Tester🚦 Simulator: Access Tester🚦 模拟器:访问测试器

Try 11 expressions on acc = BankAccount("Alice", 1000, "1234") — the class from Section 08. The matching zone of the object lights up, and you see the real result or error.

acc = BankAccount("Alice", 1000, "1234") पर 11 expressions आज़माएँ — Section 08 की class। Object का मिलता-जुलता हिस्सा चमकता है, और आपको असली नतीजा या error दिखता है।

在 acc = BankAccount("Alice", 1000, "1234")(第 08 节的类)上尝试 11 个表达式。对象中对应的区域会亮起,并显示真实的结果或错误。

Access TesterAccess Tester访问测试器11 real results11 असली नतीजे11 个真实结果
PUBLICholder
PROTECTED_log
PRIVATE__balance · __pin · __verify()
PUBLIC APIdeposit() · withdraw() · get_balance()

SectionSection第 06 节

How Name Mangling Works 🔤Name Mangling कैसे काम करता है 🔤名字改编是怎样工作的 🔤

When Python compiles the class body, any name that starts with __ (and does not end with __) is rewritten. The rule is simple: add _ClassName in front. Inside the class, self.__balance and the mangled name are the same thing. From outside, only the mangled name works.

जब Python class body को compile करता है, तो हर वह नाम जो __ से शुरू हो (और __ पर ख़त्म न हो) दोबारा लिखा जाता है। नियम आसान है: आगे _ClassName जोड़ दो। Class के अंदर self.__balance और mangled नाम एक ही चीज़ हैं। बाहर से सिर्फ़ mangled नाम चलता है।

Python 编译类体时,任何以 __ 开头(且不以 __ 结尾)的名字都会被改写。规则很简单:在前面加上 _ClassName。在类内部,self.__balance 和改编后的名字是同一个东西;在外部,只有改编后的名字才有效。

Name Mangling — Python Renames __name to _ClassName__nameName Mangling — Python __name को _ClassName__name बना देता है名字改编 — Python 把 __name 改成 _ClassName__name
Name manglingWHAT YOU WRITEआप क्या लिखते हैं你写的class BankAccount: def __init__(self): self.__balance = 1000renamedनाम बदला改名WHAT PYTHON STORESPython क्या रखता हैPython 存储的acc.__dict__ = {'_BankAccount__balance': 1000}TRIES FROM OUTSIDEबाहर से कोशिशें来自外部的尝试acc.__balance→ AttributeError (no such name)→ AttributeError (ऐसा नाम नहीं)→ AttributeError(没有这个名字)✗acc._BankAccount__balance→ 1000 (ugly, but works)→ 1000 (भद्दा, पर चलता है)→ 1000(难看,但能用)⚠Inside the class, self.__balance just works — Python renames both sides the same way.Class के अंदर self.__balance बस चलता है — Python दोनों तरफ़ एक जैसा नाम बदलता है।在类内部,self.__balance 照常可用 — Python 对两边做同样的改名。

The rule is fixed: __name in class C always becomes _C__name. Once you know it, there is no mystery — just a strong nudge to leave it alone.

नियम तय है: class C में __name हमेशा _C__name बनता है। जान लेने के बाद कोई रहस्य नहीं — बस छोड़ देने का ज़ोरदार इशारा।

规则是固定的:类 C 中的 __name 总是变成 _C__name。知道规则后就没有神秘感 — 只是一个"别碰"的强烈提示。

📋
One More Detail — __dunder__ Is Not Privateएक और बात — __dunder__ Private नहीं है再补充一点 — __dunder__ 不是私有的

Names with two underscores on both ends — __init__, __str__, __len__ — are called dunders ("double underscore"). They are not mangled and not private. Python reserves them for special methods. Only names that start with __ and do not end with __ are mangled.

दोनों तरफ़ दो underscores वाले नाम — __init__, __str__, __len__ — dunders ("double underscore") कहलाते हैं। ये mangle नहीं होते और private नहीं हैं। Python इन्हें ख़ास methods के लिए रखता है। सिर्फ़ वे नाम mangle होते हैं जो __ से शुरू हों और __ पर ख़त्म न हों।

两端 都有双下划线的名字 — __init__、__str__、__len__ — 叫作 dunder(double underscore)。它们 不会 被改编,也 不是 私有的。Python 把它们留给特殊方法。只有以 __ 开头且不以 __ 结尾的名字才会被改编。

class Report:
    def __init__(self):
        self.__init_done = True          # starts with __ but does not end with __ -> mangled

    def __str__(self):                   # dunder: begins AND ends with __ -> NOT mangled
        return "Report object"

r = Report()
print(r.__dict__)
print(str(r), r.__str__())               # dunders are normal, public names
OUTPUT
{'_Report__init_done': True} Report object Report object

🔤 Simulator: Name Mangler🔤 Simulator: Name Mangler🔤 模拟器:名字改编器

Type any class name and attribute name (or click a preset). The checker walks through Python's rule and shows the stored name, plus what happens from outside. It matched real Python on all 45 preset combinations — including odd cases like a class named ___.

कोई भी class नाम और attribute नाम लिखें (या preset दबाएँ)। Checker Python के नियम को क़दम-दर-क़दम चलाकर रखा गया नाम दिखाता है, और बाहर से क्या होता है वह भी। यह सभी 45 preset combinations पर असली Python से मिला — ___ नाम की class जैसे अजीब cases भी।

输入任意类名和属性名(或点击预设)。检查器按 Python 的规则一步步走,显示存储的名字,以及从外部访问时会怎样。它在全部 45 个预设组合上与真实 Python 一致 — 包括类名为 ___ 这样的特殊情况。

Name ManglerName Mangler名字改编器the rule, step by stepनियम, क़दम-दर-क़दम规则,逐步演示
class
self.
class:class:类:
name:नाम:名字:

SectionSection第 07 节

Private Methods — Helpers That Should Never Be Called From Outside 🧰Private Methods — ऐसे Helpers जिन्हें बाहर से कभी न बुलाया जाए 🧰私有方法 — 不应从外部调用的辅助方法 🧰

Everything about private variables applies to private methods too. A method whose name starts with __ is mangled exactly like an attribute. Use this to hide implementation details — checking, formatting, internal setup — that callers should never care about.

Private variables की हर बात private methods पर भी लागू होती है। __ से शुरू होने वाले method का नाम ठीक attribute की तरह mangle होता है। इसका इस्तेमाल उन अंदरूनी बातों को छिपाने में करें — जाँच, formatting, अंदरूनी setup — जिनसे callers को कोई मतलब नहीं होना चाहिए।

关于私有 变量 的一切同样适用于私有 方法。名字以 __ 开头的方法会像属性一样被改编。用它来隐藏实现细节 — 检查、格式化、内部设置 — 这些都是调用方不该关心的。

class User:
    def __init__(self, username, password):
        self.username = username
        self.__password_hash = self.__hash(password)   # uses a private method

    # -- Public API -------------------------------------
    def check_password(self, guess):
        return self.__password_hash == self.__hash(guess)

    # -- Private helper - never called from outside -----
    def __hash(self, s):
        # In real code you would use a proper hashing library. Toy here.
        return hex(hash(s))

alice = User("alice", "secret")
print(alice.check_password("secret"))
print(alice.check_password("guess"))

# Calling the private helper from outside -> AttributeError
try:
    alice.__hash("anything")
except AttributeError as e:
    print("Blocked:", e)

print("_User__hash" in dir(User))         # the method was renamed too
OUTPUT
True False Blocked: 'User' object has no attribute '__hash' True

The output of hash() changes between runs, but the True / False results do not — the same text always matches itself within one run.

hash() का output हर run में बदलता है, पर True / False नतीजे नहीं बदलते — एक run के अंदर वही text हमेशा ख़ुद से मेल खाता है।

hash() 的输出每次运行都会变,但 True / False 的结果不变 — 在同一次运行中,相同的文本总是与自己匹配。

Public Entry Point · Private Helpers Run InsidePublic दरवाज़ा · Private Helpers अंदर चलते हैं公有入口 · 私有辅助方法在内部运行
Public method calling private helpersOUTSIDE CODEबाहर का CODE外部代码acc.withdraw(500,"1234")class BankAccount — insideclass BankAccount — अंदरclass BankAccount — 内部PUBLICPUBLIC公有withdraw(x, pin)PRIVATEPRIVATE私有__verify(pin)if amount > balancePRIVATEPRIVATE私有__log_txn("withdraw", x)outside cannotबाहर से नहीं外部不能call a private ↘private बुला सकते ↘调用私有方法 ↘

Callers see one clean entrance. Behind it, private helpers do the real work — verify, check, log. The caller never sees them and never depends on them.

Callers को एक साफ़ दरवाज़ा दिखता है। उसके पीछे private helpers असली काम करते हैं — verify, check, log। Caller उन्हें कभी नहीं देखता, उन पर निर्भर नहीं रहता।

调用方只看到一个干净的入口。入口后面,私有辅助方法做实际工作 — 验证、检查、记录。调用方看不到它们,也不依赖它们。

🧰 Simulator: Private Helpers Step by Step🧰 Simulator: Private Helpers क़दम-दर-क़दम🧰 模拟器:私有辅助方法逐步演示

Step through acc.withdraw(200, pin). Watch the public method open frames for the private helpers on the call stack, and watch the object's __dict__ hold the mangled names. Switch to the wrong PIN to see __verify() stop everything. Recorded with sys.settrace.

acc.withdraw(200, pin) को step करें। देखें कि public method call stack पर private helpers के frames खोलता है, और object का __dict__ mangled नाम रखता है। ग़लत PIN चुनें और देखें कि __verify() सब रोक देता है। sys.settrace से रिकॉर्ड।

逐步运行 acc.withdraw(200, pin)。看公有方法在调用栈上为私有辅助方法打开帧,再看对象的 __dict__ 保存的是 改编后 的名字。切换到错误的 PIN,看 __verify() 如何拦下一切。由 sys.settrace 记录。

Private Helpers Step by StepPrivate Helpers क़दम-दर-क़दम私有辅助方法逐步演示recorded with sys.settracesys.settrace से रिकॉर्ड由 sys.settrace 记录
PIN:
CodeCode代码
Call stack (newest at the bottom)Call stack (सबसे नया नीचे)调用栈(最新的在最下面)
The object (real stored names)Object (असली रखे गए नाम)对象(真实存储的名字)
OutputOutput输出

SectionSection第 08 节

Practical Example — BankAccount With a Private Balance and PIN 🏦व्यावहारिक उदाहरण — Private Balance और PIN वाला BankAccount 🏦实例 — 带私有余额和 PIN 的 BankAccount 🏦

A realistic small class that uses all three levels together: a public API, protected logging that subclasses may extend, and private state (balance, PIN) that only the class itself touches, through private helper methods.

एक छोटी, असली-जैसी class जो तीनों स्तर एक साथ इस्तेमाल करती है: public API, protected logging जिसे subclasses बढ़ा सकती हैं, और private state (balance, PIN) जिसे सिर्फ़ class ख़ुद, private helper methods के ज़रिए, छूती है।

一个贴近现实的小类,同时使用三种级别:公有 API、子类可扩展的受保护日志,以及只有类自己通过私有辅助方法才能触碰的私有状态(余额、PIN)。

class BankAccount:
    def __init__(self, holder, opening_balance, pin):
        if not BankAccount.__is_valid_pin(pin):
            raise ValueError("PIN must be 4 digits")
        self.holder    = holder            # public - the customer's name
        self._log      = []                # protected - subclasses may extend
        self.__balance = opening_balance   # private - the whole point of a bank
        self.__pin     = pin               # private - should NEVER leak

    # == PUBLIC API - the only entry points ==
    def deposit(self, amount, pin):
        self.__verify(pin)
        if amount <= 0:
            raise ValueError("Amount must be positive")
        self.__balance += amount
        self.__log_txn("deposit", amount)

    def withdraw(self, amount, pin):
        self.__verify(pin)
        if amount > self.__balance:
            raise ValueError("Insufficient funds")
        self.__balance -= amount
        self.__log_txn("withdraw", amount)

    def get_balance(self, pin):
        # Even reading the balance requires the PIN
        self.__verify(pin)
        return self.__balance

    # == PRIVATE HELPERS - hidden implementation details ==
    def __verify(self, pin):
        if pin != self.__pin:
            raise PermissionError("Wrong PIN")

    def __log_txn(self, action, amount):
        self._log.append((action, amount))

    @staticmethod
    def __is_valid_pin(pin):
        return isinstance(pin, str) and len(pin) == 4 and pin.isdigit()


# -- Normal use - everything goes through the public API --
acc = BankAccount("Alice", opening_balance=1000, pin="1234")
acc.deposit(500, pin="1234")
acc.withdraw(200, pin="1234")
print("Balance:", acc.get_balance(pin="1234"))
print("Log:    ", acc._log)                    # protected - visible but internal

# -- Attempts to bypass the API --
try: acc.withdraw(100, pin="9999")
except PermissionError as e: print("Blocked:", e)   # wrong PIN

try: print(acc.__balance)
except AttributeError as e: print("Blocked:", e)    # direct read fails

try: acc.__verify("9999")
except AttributeError as e: print("Blocked:", e)    # the private helper cannot be called

try: BankAccount("Bob", 50, pin="12")
except ValueError as e: print("Blocked:", e)        # the private static check ran in __init__
OUTPUT
Balance: 1300 Log: [('deposit', 500), ('withdraw', 200)] Blocked: Wrong PIN Blocked: 'BankAccount' object has no attribute '__balance' Blocked: 'BankAccount' object has no attribute '__verify' Blocked: PIN must be 4 digits

The last line shows one more private helper at work: the static method __is_valid_pin (also mangled) rejects a 2-digit PIN inside __init__.

आख़िरी line एक और private helper को काम करते दिखाती है: static method __is_valid_pin (यह भी mangled) __init__ के अंदर 2 अंकों वाला PIN ठुकरा देता है।

最后一行展示了另一个私有辅助方法:静态方法 __is_valid_pin(同样被改编)在 __init__ 中拒绝了 2 位数的 PIN。

🏆
What This Class Actually Guaranteesयह Class असल में क्या पक्का करती है这个类真正保证了什么

Nobody sets the balance by accident — every change goes through deposit or withdraw, which check the PIN and the amount. Nobody reads the balance without the PIN. Nobody calls __verify by its plain name. The class shows a small, safe surface, and everything else is renamed out of the way. That is encapsulation in Python — as long as nobody deliberately uses the mangled names.

कोई भी balance ग़लती से नहीं बदलता — हर बदलाव deposit या withdraw से होता है, जो PIN और amount जाँचते हैं। PIN के बिना कोई balance नहीं पढ़ता। कोई __verify को सादे नाम से नहीं बुलाता। Class एक छोटी, सुरक्षित सतह दिखाती है, और बाक़ी सब नाम बदलकर रास्ते से हटा दिया गया है। Python में यही encapsulation है — जब तक कोई जान-बूझकर mangled नाम इस्तेमाल न करे।

没有人会无意中改动余额 — 每次修改都经过 deposit 或 withdraw,它们会检查 PIN 和金额。没有 PIN 就读不到余额。没有人能用原名调用 __verify。类只暴露一个小而安全的表面,其余一切都被改名挪开。这就是 Python 中的 封装 — 前提是没人故意使用改编后的名字。

🏦 Simulator: Bank Vault🏦 Simulator: Bank Vault🏦 模拟器:银行金库

Use the public API with the right or wrong PIN. Then try the three tricks: write acc.__balance from outside, read it back, and cheat with the mangled name. The live __dict__ shows what really changes. Checked against the real class on 300 random sequences.

सही या ग़लत PIN के साथ public API इस्तेमाल करें। फिर तीन चालें आज़माएँ: बाहर से acc.__balance लिखें, उसे वापस पढ़ें, और mangled नाम से धोखा दें। Live __dict__ दिखाता है कि असल में क्या बदलता है। असली class से 300 random sequences पर जाँचा गया।

用正确或错误的 PIN 调用公有 API。然后试试三个把戏:从外部写 acc.__balance、再读回来、以及用改编后的名字作弊。实时的 __dict__ 显示真正改变了什么。已在 300 个随机序列上与真实类核对。

Bank VaultBank Vault银行金库public API vs trickspublic API बनाम चालें公有 API 与小把戏
Public API:Public API:公有 API:
Tricks:चालें:把戏:
Amount:Amount:金额:
PIN:
This line will runयह line चलेगी将要运行的代码
🔒 real balanceअसली balance真实余额:
ConsoleConsole控制台

SectionSection第 09 节

Public API vs Private Implementation — The Contract 🤝Public API बनाम Private Implementation — अनुबंध 🤝公有 API 与私有实现 — 契约 🤝

🔏 Private · implementation detailsPrivate · अंदरूनी बातें私有 · 实现细节
Named with __x (two leading underscores)__x नाम (आगे दो underscores)以 __x 命名(前面两个下划线)
Python renames them → _ClassName__xPython नाम बदलता है → _ClassName__xPython 改名为 → _ClassName__x
You are free to change them any timeआप इन्हें कभी भी बदल सकते हैं你可以 随时修改 它们
Callers must not depend on themCallers को इन पर निर्भर नहीं होना चाहिए调用方不应依赖它们
Examples: __balance, __pin, __hash()उदाहरण: __balance, __pin, __hash()例如:__balance、__pin、__hash()
📱 Public · the API contractPublic · API अनुबंध公有 · API 契约
No prefix (deposit, get_balance)कोई prefix नहीं (deposit, get_balance)无前缀(deposit、get_balance)
Callable from anywhere — that is the pointकहीं से भी बुला सकते हैं — यही मक़सद है任何地方都能调用 — 这正是目的
You promise not to break them without a new versionआप वादा करते हैं कि इन्हें नहीं तोड़ेंगे (नए version के बिना)你 承诺不会破坏它们(除非发布新版本)
Every caller safely depends on themहर caller इन पर सुरक्षित रूप से निर्भर है每个调用方都可以放心依赖
Change them → everyone who uses them must updateइन्हें बदलें → इस्तेमाल करने वाले सबको update करना होगा修改它们 → 所有使用者都得跟着更新
🔑
Why This Split Mattersयह बँटवारा क्यों ज़रूरी है为什么这种划分很重要

The whole point of private members is freedom. Once you publish a public method, changing its signature breaks every caller. But you can swap the private implementation any time — add caching, change the algorithm, split one helper into three — as long as the public API behaves the same. Private members are the space where you can refactor without fear.

Private members का पूरा मक़सद आज़ादी है। एक बार public method जारी कर दिया, तो उसका signature बदलने से हर caller टूटता है। पर private implementation आप कभी भी बदल सकते हैं — caching जोड़ें, algorithm बदलें, एक helper को तीन में बाँटें — बस public API वैसा ही व्यवहार करे। Private members वह जगह हैं जहाँ आप बिना डर refactor कर सकते हैं।

私有成员的全部意义在于 自由。公有方法一旦发布,改变它的签名就会破坏所有调用方。但私有实现可以随时更换 — 加缓存、换算法、把一个辅助方法拆成三个 — 只要公有 API 的行为不变。私有成员就是你可以放心重构的空间。


SectionSection第 10 节

Common Mistakes (and Fixes) 🩹आम ग़लतियाँ (और उनके हल) 🩹常见错误(及修正)🩹

Mistakeग़लती错误What goes wrongक्या बिगड़ता है会出什么问题Fixहल修正
Reading a private attribute from outside by its mangled nameबाहर से mangled नाम से private attribute पढ़ना从外部用改编名读取私有属性acc._BankAccount__balance works, but ties your code to the class nameacc._BankAccount__balance चलता है, पर आपका code class के नाम से बँध जाता हैacc._BankAccount__balance 能用,但让你的代码绑死在类名上Don't. Add a public getter method insteadऐसा न करें। इसकी जगह public getter method जोड़ें不要这样做。改为添加公有 getter 方法
Using __name when you mean "internal, but subclasses too""अंदरूनी, पर subclasses के लिए भी" के लिए __name इस्तेमाल करना想要"内部但子类也能用"却用了 __nameSubclasses cannot easily read or change it — mangling gives each class its own nameSubclasses आसानी से पढ़ या बदल नहीं सकतीं — mangling हर class को अलग नाम देता है子类无法轻松读取或修改 — 改编让每个类有各自的名字Use _name (one underscore)_name (एक underscore) इस्तेमाल करें使用 _name(一个下划线)
Treating private as securityPrivate को security मानना把私有当作安全措施Anyone can still read _ClassName__xकोई भी _ClassName__x पढ़ सकता है任何人都能读取 _ClassName__xDon't keep secrets in an attribute expecting them to be hiddenछिपे रहने की उम्मीद में secrets को attribute में न रखें不要指望属性能把秘密藏起来
Naming things __init or __strनाम __init या __str रखना起名为 __init 或 __strConfusing — looks like a dunder, but is still mangledभ्रम — dunder जैसा दिखता है, पर फिर भी mangle होता है容易混淆 — 看着像 dunder,但仍会被改编Use a clear name like _init_helper or __init_helper_init_helper या __init_helper जैसा साफ़ नाम रखें用清晰的名字,如 _init_helper 或 __init_helper
obj.__attr = value from outsideबाहर से obj.__attr = value在外部写 obj.__attr = valueMakes a new attribute named literally __attr (no mangling outside the class)__attr नाम का नया attribute बनता है (class के बाहर mangling नहीं)新建一个名字就叫 __attr 的属性(类外不改编)Never write double-underscore names from outsideबाहर से दो underscore वाले नाम कभी न लिखें永远不要在外部写双下划线名字
Using two underscores everywhereहर जगह दो underscores到处都用双下划线Subclasses cannot take part; mangled names clutter __dict__Subclasses हिस्सा नहीं ले पातीं; mangled नाम __dict__ भर देते हैं子类无法参与;改编名让 __dict__ 杂乱Default to _x; keep __x for truly private stateडिफ़ॉल्ट _x; __x सिर्फ़ सच में private state के लिए默认用 _x;__x 留给真正私有的状态
Making everything private "just in case""बस यूँ ही" सब private बनाना"以防万一"把一切都设为私有The class becomes hard to extend and to testClass को बढ़ाना और test करना मुश्किल हो जाता है类变得难以扩展和测试Start with a small public API; make things private only for a reasonछोटी public API से शुरू करें; किसी वजह से ही private बनाएँ从小的公有 API 开始;有理由时才设为私有

The obj.__attr = value trap, run for real:

obj.__attr = value वाला जाल, असली run में:

obj.__attr = value 陷阱的真实运行:

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

acc = BankAccount(1000)
acc.__balance = 0                  # outside the class: NO mangling happens here
print(acc.__dict__)                # a brand-new attribute named literally "__balance"
print(acc._BankAccount__balance)   # the real balance is untouched
OUTPUT
{'_BankAccount__balance': 1000, '__balance': 0} 1000

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Four of these mistakes, run for real. Buggy code on the left, fixed code on the right, changed lines marked. Two of them give no error — just a wrong or unsafe result.

इनमें से चार ग़लतियाँ, असली run में। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। इनमें से दो कोई error नहीं देतीं — बस ग़लत या असुरक्षित नतीजा।

其中四个错误的真实运行。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。其中两个 不报错 — 只是结果错误或不安全。

Bug DoctorBug DoctorBug 医生4 bugs · real outputs4 bugs · असली outputs4 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正

SectionSection第 11 节

Quick Reference 📋Quick Reference 📋速查表 📋

PrefixPrefix前缀Calledनाम称呼What Python doesPython क्या करता हैPython 做什么When to useकब इस्तेमाल करें何时使用
xpublicNothing — fully accessibleकुछ नहीं — पूरी तरह उपलब्ध什么都不做 — 完全可访问The class's official APIClass की official API类的正式 API
_xprotected (convention)protected (परंपरा)受保护(约定)Nothing — a signal to peopleकुछ नहीं — लोगों के लिए संकेत什么都不做 — 给人看的信号Internal, but subclasses may use itअंदरूनी, पर subclasses इस्तेमाल कर सकती हैं内部使用,但子类可以用
__xprivate (name-mangled)private (name-mangled)私有(名字改编)Renames to _ClassName__x_ClassName__x नाम कर देता है改名为 _ClassName__xTruly internal — nobody else touches itसच में अंदरूनी — कोई और नहीं छूता真正内部 — 别人不碰
__x__dunder (special)dunder (ख़ास)dunder(特殊)Nothing — reserved for special methodsकुछ नहीं — ख़ास methods के लिए आरक्षित什么都不做 — 留给特殊方法Only for Python's own special methodsसिर्फ़ Python के अपने ख़ास methods के लिए只用于 Python 自己的特殊方法
_x_unusual — avoidअसामान्य — बचें少见 — 避免Nothing specialकुछ ख़ास नहीं没有特殊处理No convention — don't invent oneकोई परंपरा नहीं — नई न बनाएँ没有约定 — 别自创
Taskकाम任务SyntaxSyntax语法Notesनोट说明
Declare a private attributePrivate attribute declare करना声明私有属性self.__balance = 1000Only inside the classसिर्फ़ class के अंदर只在类内部
Declare a private methodPrivate method declare करना声明私有方法def __verify(self, pin):Called as self.__verify(...) insideअंदर self.__verify(...) से बुलाएँ在内部用 self.__verify(...) 调用
See the mangled namesMangled नाम देखना查看改编后的名字obj.__dict__Shows _ClassName__x keys_ClassName__x keys दिखाता है显示 _ClassName__x 键
Reach a private (don't)Private तक पहुँचना (न करें)访问私有成员(别这样做)obj._ClassName__xCheating — reject it in code reviewधोखा — code review में ठुकराएँ作弊 — 代码审查时拒绝
Provide a public getterPublic getter देना提供公有 getterdef get_x(self): return self.__xThe right way to expose a private valuePrivate value दिखाने का सही तरीक़ा暴露私有值的正确方式
Modern getter/setterआधुनिक getter/setter现代 getter/setter@propertyPreferred over get/set methods — covered in an upcoming tutorialget/set methods से बेहतर — आने वाले tutorial में优于 get/set 方法 — 将在后续教程讲解

SectionSection第 12 节

Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆

🔒 Private Variables and Private Methods — Rules to RememberPrivate Variables और Private Methods — याद रखने वाले नियम私有变量与私有方法 — 必须记住的规则
1
Python has no real private keyword. It has naming conventions and name mangling. Both are signals, not security. Anyone determined can still reach in.
Python में कोई असली private keyword नहीं है। इसमें naming conventions और name mangling हैं। दोनों संकेत हैं, security नहीं। ठान चुका कोई भी अंदर पहुँच सकता है।
Python 没有真正的 private 关键字。它只有命名约定和名字改编。两者都是信号,不是安全措施。有心人照样能伸手进去。
2
One underscore _x is a polite convention — "internal, don't touch unless you know what you're doing". Python does not enforce it. Every Python developer respects it.
एक underscore _x एक विनम्र परंपरा है — "अंदरूनी, जब तक पता न हो, न छुएँ"। Python इसे लागू नहीं करता। हर Python developer इसे मानता है।
单下划线 _x 是礼貌的约定 — "内部使用,不懂别碰"。Python 不强制,但每个 Python 开发者都会遵守。
3
Two underscores __x trigger name mangling: Python renames it to _ClassName__x. Accidental access from outside fails with AttributeError. Deliberate access still works, but it shouts "I am cheating".
दो underscores __x name mangling चालू करते हैं: Python नाम _ClassName__x कर देता है। बाहर से ग़लती से पहुँचना AttributeError देता है। जान-बूझकर पहुँचना फिर भी चलता है, पर चिल्लाकर कहता है "मैं धोखा दे रहा हूँ"।
双下划线 __x 触发名字改编:Python 把它改名为 _ClassName__x。从外部无意访问会得到 AttributeError。故意访问仍然可行,但等于大喊"我在作弊"。
4
Names with two underscores on both sides (__init__, __str__) are not private. They are Python's special (dunder) names. Never invent your own __myname__.
दोनों तरफ़ दो underscores वाले नाम (__init__, __str__) private नहीं हैं। ये Python के ख़ास (dunder) नाम हैं। अपना __myname__ कभी न बनाएँ।
两端都有双下划线 的名字(__init__、__str__)不是私有的。它们是 Python 的特殊(dunder)名字。永远不要自创 __myname__。
5
Default to _x. Use __x only for a specific reason — usually sensitive state (a PIN, a token) or a name that must not clash with a subclass's attribute of the same name.
डिफ़ॉल्ट _x रखें। __x किसी ख़ास वजह से ही लें — आमतौर पर संवेदनशील state (PIN, token) या ऐसा नाम जो subclass के उसी नाम वाले attribute से न टकराए।
默认用 _x。只在有具体理由时才用 __x — 通常是敏感状态(PIN、令牌),或必须与子类同名属性避免冲突的名字。
6
Every class has a public API (a small set of methods you promise to keep working) and a private implementation (everything else, free to change). Private exists to give you room to refactor.
हर class में एक public API (methods का छोटा सेट जिन्हें चलाते रहने का आप वादा करते हैं) और एक private implementation (बाक़ी सब, बदलने को आज़ाद) होता है। Private refactor करने की जगह देने के लिए है।
每个类都有 公有 API(你承诺保持可用的一小组方法)和 私有实现(其余一切,可以自由修改)。私有的存在就是为了给你重构的空间。
7
When a caller needs to read or change a private value, add a public getter/setter method. Never offer the mangled name as an "escape hatch".
जब caller को private value पढ़नी या बदलनी ज़रूरी हो, तो public getter/setter method जोड़ें। Mangled नाम को "चोर दरवाज़ा" बनाकर कभी न दें।
当调用方 确实需要 读取或修改私有值时,添加公有的 getter/setter 方法。永远不要把改编名当作"后门"提供出去。
8
Private methods are for helpers nobody outside your class should call — checks (__verify), formatting (__hash), internal bookkeeping (__log_txn). Keep them small; they are the ingredients of your public methods.
Private methods उन helpers के लिए हैं जिन्हें class के बाहर कोई न बुलाए — जाँच (__verify), formatting (__hash), अंदरूनी हिसाब (__log_txn)। इन्हें छोटा रखें; ये आपके public methods की सामग्री हैं।
私有方法用于 类外任何人都不该调用的辅助逻辑 — 检查(__verify)、格式化(__hash)、内部记账(__log_txn)。保持它们小巧;它们是公有方法的原料。
9
Never rely on private as a security boundary. Passwords and keys need real protection (hashing, encryption, secure storage) — not just __password.
Private को कभी security की दीवार न मानें। Passwords और keys को असली सुरक्षा चाहिए (hashing, encryption, सुरक्षित storage) — सिर्फ़ __password नहीं।
永远不要把私有当作 安全边界。密码和密钥需要真正的保护(哈希、加密、安全存储)— 而不只是 __password。
10
When in doubt, ask: "Can I change this next month without hurting anyone?" Yes → private (__x). Subclasses may need it → protected (_x). Callers depend on it → public. Match the name to your promise.
शक हो तो पूछें: "क्या मैं इसे अगले महीने बिना किसी को नुक़सान पहुँचाए बदल सकता हूँ?" हाँ → private (__x)। Subclasses को चाहिए → protected (_x)। Callers निर्भर हैं → public। नाम को अपने वादे से मिलाएँ।
拿不准时问自己:"下个月我能改掉它而不伤害任何人吗?"能 → 私有(__x)。子类可能需要 → 受保护(_x)。调用方依赖它 → 公有。让名字与你的承诺相符。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

No prefix = public API. One underscore = "internal, please knock" — only a convention. Two underscores = name mangling: inside class C, __x is stored as _C__x, so the plain name fails from outside. Dunders like __init__ are never mangled. None of this is security — it is a clear signal. Give callers a small public API, keep helpers private, and change private data only through methods.

कोई prefix नहीं = public API। एक underscore = "अंदरूनी, कृपया दस्तक दें" — सिर्फ़ परंपरा। दो underscores = name mangling: class C के अंदर __x _C__x नाम से रखा जाता है, इसलिए बाहर से सादा नाम fail होता है। __init__ जैसे dunders कभी mangle नहीं होते। इनमें से कुछ भी security नहीं — यह एक साफ़ संकेत है। Callers को छोटी public API दें, helpers को private रखें, और private data को सिर्फ़ methods से बदलें।

无前缀 = 公有 API。单下划线 = "内部使用,请敲门" — 只是约定。双下划线 = 名字改编:在类 C 中,__x 存为 _C__x,所以原名在外部会失败。像 __init__ 这样的 dunder 从不改编。这些都不是安全措施 — 而是清晰的信号。给调用方一个小而公有的 API,把辅助方法设为私有,只通过方法修改私有数据。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.