一、问题描述
在设计一类加法的数据结构中,定义 Foo
对象可以拥有自己的取值 value
以及一系列可以通过累加得到该 Foo
对象的取值 value
的加法因子 factors
。
为简化对 Foo
对象的维护,希望每次获取 Foo
的取值 value
时,可以直接通过其加法因子列表元素的累加来获取其 value
,而不需要每次手动对其 value
进行更新,因此考虑通过装饰器 @property
来实现该功能,如以下代码所示。
class Foo:
def __init__(self, value, factors=None):
self.value = value
self.factors = [self] if factors is None else factors
@property
def value(self):
return sum([factor.value for factor in self.factors]) if len(self.factors) > 1 else self.value
foo1 = Foo(value=3)
foo2 = Foo(value=5)
foo = Foo(value=8, factors=[foo1, foo2])
print(f"The value of foo1 is: {foo1.value}.")
print(f"The value of foo2 is: {foo2.value}.")
print(f"The value of foo is: {foo.value}.")
然而,运行该段代码后,程序报错 AttributeError
,详细报错信息如下:
Traceback (most recent call last):
File "attribute_property.py", line 11, in <module>
foo1 = Foo(value=3)
File "attribute_property.py", line 3, in __init__
self.value = value
AttributeError: can't set attribute
二、问题分析
出现该报错的原因是因为 property
装饰器作为特性重写了 value
属性,因此当创建新的 Foo
对象时,line 3 实际上将找到 @property
重写的 value
方法,而由于 @property
实际上只实现了 @getter
功能,因此可认为它只实现了可读属性的功能,而并没有实现 @setter
的功能。简单来说,这个时候是不能为它赋值的。(关于 @getter
、@setter
等相关装饰器内容请参见相关博客。)
三、解决方案
从上例中可以看到,此时我们是无法使用特性(property
)直接覆盖同名属性(attribute
)的。
一个简单的解决方案是将原来的 value
属性设置为保护属性,即在其变量名前添加下划线成为 _value
。对应地,将 @property
实现的类方法中所返回的 value
属性更改为 _value
。既然此时这二者都不同名了,自然也不会出现设置属性报错的问题了。
修改后的代码及运行结果如下所示:
class Foo:
def __init__(self, value, factors=None):
self._value = value
self.factors = [self] if factors is None else factors
@property
def value(self):
return sum([factor.value for factor in self.factors]) if len(self.factors) > 1 else self._value
foo1 = Foo(value=3)
foo2 = Foo(value=5)
foo = Foo(value=8, factors=[foo1, foo2])
print(f"The value of foo1 is: {foo1.value}.")
print(f"The value of foo2 is: {foo2.value}.")
print(f"The value of foo is: {foo.value}.")
---------------------------------------------
The value of foo1 is: 3.
The value of foo2 is: 5.
The value of foo is: 8.
此外,如果我们想要直接从外部重新对 _value
进行赋值,也可以通过添加 @setter
方法来实现。
class Foo:
def __init__(self, value, factors=None):
self._value = value
self.factors = [self] if factors is None else factors
@property
def value(self):
return sum([factor.value for factor in self.factors]) if len(self.factors) > 1 else self._value
@value.setter
def value(self, value):
self._value = value
foo1 = Foo(value=3)
foo2 = Foo(value=5)
foo = Foo(value=8, factors=[foo1, foo2])
print(f"The value of foo1 is: {foo1.value}.")
print(f"The value of foo2 is: {foo2.value}.")
print(f"The value of foo is: {foo.value}.")
foo1.value = 4
foo2.value = 6
print()
print(f"The value of foo1 is: {foo1.value}.")
print(f"The value of foo2 is: {foo2.value}.")
print(f"The value of foo is: {foo.value}.")
---------------------------------------------
The value of foo1 is: 3.
The value of foo2 is: 5.
The value of foo is: 8.
The value of foo1 is: 4.
The value of foo2 is: 6.
The value of foo is: 10.
上例中,当我们分别对 foo1
和 foo2
的 value
进行赋值,该过程将调用 Foo
的 @setter
方法,对保护属性 _value
进行重新赋值。而当计算 foo
的 value
时,由于其加法因子 foo1
和 foo2
的 value
均改变,其 value
的取值随之发生动态变化。