双下划线不是封装手段,而是名称改写机制;__var会被自动重命名为_ClassName__var,但你根本没在写这个名,它发生在编译期,导致动态访问失效、工具链误判、继承混乱及序列化失败。

双下划线不是封装手段,而是名称改写机制;用它来“隐藏变量”会破坏可读性、干扰工具链、引发继承和序列化意外行为。
__var 会被自动重命名为 _ClassName__var,但你根本没在写这个名
Python 解释器在编译类定义时,会把所有形如 self.__x 的赋值或访问,悄悄替换成 self._MyClass__x。这不是运行时检查,也不是访问控制——它发生在字节码生成阶段。
-
hasattr(obj, '__x')返回False,因为原始名字压根不存在于实例字典中 -
print(obj.__dict__)显示的是'_MyClass__x': 123,不是'__x': 123 - 动态操作(如
getattr(obj, '__x')或setattr(obj, '__x', 999))全部失效,必须用改写后的完整名 - IDE 自动补全、mypy 类型推导、PyCharm 的字段跳转,都基于改写后的名字,容易让你误以为“字段丢了”
from module import * 不忽略 __var,反而让它彻底不可见
单下划线 _helper 在 from mymod import * 中被明确跳过,这是 PEP 8 支持的约定;但双下划线 __internal 没有这种待遇。
- 模块级
__version__是特例(约定俗成),但__cache不是——它不会被导入,也不会被重命名,只是“静默消失” - 如果你在模块里写了
__config = {...},然后执行from mymod import *,__config不会进入当前命名空间,且没有任何警告 - 更糟的是:如果该模块被其他包通过
import mymod引入,调用方仍得用mymod.__config访问——但此时它又没被重命名,语义不一致
子类继承时你以为在覆盖,其实新建了一个字段
双下划线的本意是避免子类意外覆盖父类内部状态,但它带来的隔离是“过度”的——父类和子类的同名 __field 实际指向两个完全无关的键。
- 父类中
self.__state = 'ready'→ 存为_Parent__state - 子类中
self.__state = 'done'→ 存为_Child__state,跟父类字段毫无关系 - 结果是:父类方法调用
self.__state读到的永远是自己的值,子类无法修正、也无法复用该逻辑 - 调试时用
dir(obj)会看到两个字段,但你很难意识到它们属于不同作用域
序列化、测试、dataclass 都不认 __var
几乎所有主流工具链都默认绕过双下划线字段,除非你显式干预。
-
json.dumps(obj.__dict__)不包含任何__xxx字段(因为它们不在__dict__里) -
dataclasses.asdict()和pydantic.BaseModel.dict()默认忽略双下划线字段,需用field(repr=False)或exclude_unset=True等额外配置 -
mock.patch('mymodule.MyClass.__secret')失败,必须写成mock.patch('mymodule.MyClass._MyClass__secret') - logging 或 debug 打印对象时,
__str__里若直接拼self.__x,可能因名称改写导致AttributeError
真正需要“避免子类覆盖”的场景极少;绝大多数所谓“私有”,只是想表达“别直接用”。这时候 _var 更直白、更兼容、更符合工具预期——而 __var 像一把钝刀,削不动问题,还容易割到自己手。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











