Python Morsels:制作一个只读属性

146 阅读4分钟

转载

我们如何在 Python 中制作一个只读属性

一个属性就像一个自动更新的属性

我们有一个叫做Square 的类,它接受一个length 和一个可选的color ,并将其作为lengthcolor 的属性存储。

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 可以被改变,但lengtharea 不能被改变呢?

>>> s1.color = 'purple' >>> s1.color 'purple'

我们想让lengtharea 属性成为只读属性。

事实证明,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 中建立一个只读属性,你可以把你的_属性_变成一个属性,委托给一个名字_几乎_相同的属性,但在它的名字前有一个下划线,以说明它是私有的惯例