Python双下划线属性触发名称改写(如__value→_ClassName__value),旨在避免继承命名冲突,非访问控制;仍可直接访问改名后属性,但原名不可用。

Python中双下划线私有属性不为“隐藏”,只为避免覆盖
双下划线开头的属性(如__value)根本不是为了“阻止访问”,而是解决继承场景下的命名冲突。它触发的是名称改写(name mangling),不是访问控制。你仍然能通过_ClassName__value拿到它,只是不能用原名直接点出来。
常见错误现象:AttributeError: 'MyClass' object has no attribute '__value'——这不是报错“不能访问”,而是报错“找不到这个名字”,因为名字已经被Python悄悄改了。
- 只对类定义内部、且以
__开头+不以__结尾的标识符生效(__init__、__str__等魔术方法完全不受影响) - 改写规则严格:变成
_类名__原名,中间是单下划线+原始类名+双下划线+原始属性名 - 子类定义同名
__value,会生成_SubClass__value,和父类的_ParentClass__value互不干扰
为什么不能只用单下划线\_?
_value只是约定,不改名、不限制、不防覆盖。它在模块导入时会被from module import *忽略,但其他所有访问路径都畅通无阻——包括子类继承后直接覆写。
使用场景差异明显:
立即学习“Python免费学习笔记(深入)”;
- 想提示“别乱用,但允许调试/测试时临时访问” → 用
_value - 想确保子类加个
__value不会意外覆盖父类逻辑 → 必须用__value - 想彻底禁止外部调用(比如敏感凭证)→ 单靠下划线做不到,得配合
property+ 文档 + 运行时校验
名称改写在继承中真实起效的案例
看这个典型例子:
class Parent:
def __init__(self):
self.__data = "parent"
<p>class Child(Parent):
def <strong>init</strong>(self):
super().<strong>init</strong>()
self.__data = "child"</p><p>c = Child()
print(c._Parent__data) # 输出 "parent"
print(c._Child__data) # 输出 "child"
如果这里用的是_data,子类赋值会直接覆盖父类的_data;而用__data,两个值各自独立存在,靠改名隔离。这就是它存在的唯一强理由。
注意:__data在__dict__里根本不会出现,你看到的是_Parent__data和_Child__data两个键。
调试和反射时容易踩的坑
名称改写会让dir()、vars()、IDE自动补全、甚至pdb调试器都“看不见”原名。你查c.__dict__时满屏都是_ClassName__xxx,容易误判为“属性没设上”。
更隐蔽的问题:
- 用
getattr(c, '__data', None)一定返回None,必须写成getattr(c, '_Child__data', None) - 序列化(如
json.dumps)默认不处理这些改名后的字段,除非显式指定default函数 - mock测试时,
patch('mymodule.MyClass.__data')会失败,得打到'mymodule.MyClass._MyClass__data'
真正难的从来不是“怎么写私有”,而是“怎么让别人(包括未来的你自己)在不翻源码的情况下,猜到那个被改过的名字到底长啥样”。


















