这并不是那么简单,因为它严重依赖 Python descriptor protocol,另请参阅 Descriptor HOW-TO which refers to property as well。但我会尽量用通俗易懂的方式来解释。
你有一个类(除了隐式超类object 继承的和一些自动包含的东西)2 个属性:
>>> P.__dict__
mappingproxy({'__init__': <function __main__.P.__init__>,
'x': <property at 0x2842664cbd8>})
为了讨论,我删除了自动添加的属性。您可以随时根据需要添加或替换属性:
>>> P.y = 1000
>>> P.__dict__
mappingproxy({'__init__': <function __main__.P.__init__>,
'x': <property at 0x2842664cbd8>,
'y': 1000})
但是当您创建一个实例时,该实例将只有一个属性_P__x(插入_P,因为以__ 开头且不以__ 结尾的变量是名称混乱的):
>>> p = P(10)
>>> p.__dict__
{'_P__x': 10}
您还可以为实例添加几乎(只是几乎因为描述符协议拦截某些操作 - 见下文)任何属性:
>>> p.y = 100
>>> p.__dict__
{'_P__x': 10, 'z': 100}
这就是描述符协议发挥作用的地方。如果您访问实例上的某个属性,它首先会查看该实例是否具有该属性。如果实例没有该属性,它将查看类 - 但通过描述符协议!所以当你访问self.x时,大致相当于:type(self).x.__get__(self):
>>> p.x
10
>>> type(p).x.__get__(p)
10
同样用self.x = 200设置属性会调用type(self).x.__set__(self, 200):
>>> p.x = 200
>>> p.x
200
>>> type(p).x.__set__(p, 100)
>>> p.x
100
@property 将通过描述符协议 any 拦截对self 上的x 的访问。因此,您不能使用名称 x 将 actual 值存储在实例上,因为它总是会进入 @property 和 @x.setter (还有 x.deleter 但你没有t 实现了该类的一个)功能。所以你必须使用另一个名字来存储变量。
它通常以相同的名称但一个前导下划线存储(也便于维护)。实际上,使用两个前导下划线并不是一个好习惯,因为这会导致难以对类进行子类化和修改 x 属性 - 无需自己对变量名进行名称修改。