转载
我们如何在 Python 中制作一个只读属性?
一个属性就像一个自动更新的属性
我们有一个叫做Square 的类,它接受一个length 和一个可选的color ,并将其作为length 和color 的属性存储。
class Square: def __init__(self, length, color=None): self.length = length self.color = color def __repr__(self): return f"Square({self.length!r}, {self.color!r})" @property def area(self): return self.length**2
如果我们做一个Square 对象(在这个例子中只有一个长度)。
>>> s1 = Square(4)
我们可以看到这个Square 对象的字符串表示中的长度和颜色。
>>> s1 Square(4, None)
这个类也有一个area 属性。
`@property def area(self): return self.length**2`
属性是一种类似于虚拟属性的东西。
每次我们访问area 属性时,一个函数将被执行,其返回值将被反馈给我们。
>>> s1.area 16
这个函数的调用是自动发生的,只是通过_访问_ area 属性。
因此,如果我们改变一个Square 对象的length 。
>>> s1.length = 3
area 似乎也会自动改变。
>>> s1.area 9
属性可以做成只读属性
如果我们想让我们的Square 对象的color 可以被改变,但length 和area 不能被改变呢?
>>> s1.color = 'purple' >>> s1.color 'purple'
我们想让length 和area 属性成为只读属性。
事实证明,area 属性已经是只读的了。
>>> s1.area = 100 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
我们不能分配给area ,因为属性默认是只读的。
我们可以尝试把我们的length 属性变成一个属性,在我们的类定义中加入这个。
`@property def length(self): return self.length`
我们使用property 装饰器来创建一个名为length 的属性,这个属性在访问时返回length 属性。
但这个length 属性并不_完全_有效。
>>> s1 = Square(4) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/trey/shapes.py", line 3, in __init__ self.length = length AttributeError: can't set attribute
它不工作是因为在我们的初始化方法中,我们分配给了length 。
`def __init__(self, length, color=None): self.length = length self.color = color`
但是length 是一个没有setter的属性,所以它是只读的(所有属性默认都是只读的)。
我们的属性正在访问与我们的属性存储在同一地方的数据。 这是不可能的!没有任何东西可以区分我们的属性名和同名的属性,所以我们有一个名称冲突。
我们需要我们的属性名称与我们_实际存储_数据的属性不同。 我们不想重命名我们的属性,所以让我们重命名我们的属性。
为内部属性使用下划线前缀
我们需要一个名字不同于length 的属性。
当你有一个代表不应该被直接访问的内部数据的属性时 (被你的类之外的代码访问),在 Python 中通常会在属性名前加一个下划线 (_)。
PEP 8 也指出了这个惯例。"只对非公共方法和实例变量使用一个前导下划线"。
因此,我们将把存储我们长度的属性重命名为_length: 所以,我们会说。
`def __init__(self, length, color=None): self._length = length self.color = color`
而我们的属性现在将访问self._length ,以获得长度。
`@property def length(self): return self._length`
这里是完整的类。
class Square: def __init__(self, length, color=None): self._length = length self.color = color def __repr__(self): return f"Square({self.length!r}, {self.color!r})" @property def area(self): return self.length**2 @property def length(self): return self._length
现在当我们做一个Square 对象时,我们会看到有一个length 。
>>> s1 = Square(4) >>> s1.length 4
但我们不能分配给它,因为它是一个属性。
>>> s1.length = 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
单个下划线的前缀只是一个惯例
请注意,这个单下划线前缀只是一个约定,所以我们仍然可以访问_length 属性,如果我们想的话。
>>> s1._length 4
事实上,我们甚至可以改变它。
>>> s1._length = 5
这就改变了我们的Square 对象上的length 。
>>> s1.length 5
但是当其他 Python 程序员看到对带下划线的属性的赋值时,他们会看到这段代码并认为发生了一些奇怪的事情:我们正在改变一个对象的一些内部细节。根据惯例,带下划线的属性应该是私有的,所以看到对带下划线属性的赋值是不常见的。
总结
如果你需要在 Python 中建立一个只读属性,你可以把你的_属性_变成一个属性,委托给一个名字_几乎_相同的属性,但在它的名字前有一个下划线,以说明它是私有的惯例。