Python为何允许同名实例属性?双下划线属性访问与赋值异常问题解析
这是个非常有意思的问题,核心原因在于Python的**名称修饰(Name Mangling)**机制,咱们来一步步拆解清楚:
1. 第一次访问报错的原因
当你在类内部定义以双下划线开头的属性(比如__secret)时,Python会自动对这个属性名做「名称修饰」——它会把属性名修改为_类名__属性名的格式,目的是避免子类意外覆盖父类的私有属性,或者外部代码不小心修改到类内部的属性。
看你第一个例子的代码:
class genericClass: def __init__(self): self.__secret = "secret" generic_object = genericClass() print(generic_object.__secret) # 报错:AttributeError
这里真正存储的属性其实是_genericClass__secret,而不是你写的__secret,所以直接访问generic_object.__secret自然会提示属性不存在。
2. 第二次赋值后能访问的原因
当你执行generic_object.__secret = "not a secret anymore"时,你并不是在修改原来的那个__secret(也就是被修饰后的_genericClass__secret),而是给这个实例新增了一个完全独立的、名为__secret的公共属性。
这个新属性和类内部定义的那个没有任何关联——因为名称修饰只在类定义的内部生效,在类外部直接给实例赋值双下划线开头的属性时,Python不会做任何修饰,只会把它当作普通的属性名存储。
3. 实例确实存在两个不同的__secret相关属性
是的,这时候你的实例generic_object同时拥有两个属性:
- 类内部定义的、被修饰后的私有属性:
_genericClass__secret,值为"secret" - 你后来新增的公共属性:
__secret,值为"not a secret anymore"
你可以用dir()函数验证这一点:
class genericClass: def __init__(self): self.__secret = "secret" generic_object = genericClass() generic_object.__secret = "not a secret anymore" print(dir(generic_object))
输出结果里会同时出现_genericClass__secret和__secret。也可以分别访问这两个属性来确认:
print(generic_object._genericClass__secret) # 输出: secret print(generic_object.__secret) # 输出: not a secret anymore
总结
Python的双下划线属性不是严格意义上的「私有属性」(你依然可以通过_类名__属性名的方式访问),它本质是一种名称修饰机制,用来减少属性名冲突的概率。而在类外部直接给实例赋值双下划线开头的属性时,不会触发这个修饰机制,只会创建一个新的普通属性,这就是你看到两种不同现象的根本原因。
内容的提问来源于stack exchange,提问作者Emi Buliga

