Property setters for compound assignment?
考虑下面的简单示例类,该类的属性在调用时公开一些内部数据的修改版本:
1 2 3 4 5 6 7 8 9 10 11 12
| class Foo(object):
def __init__(self, value, offset=0):
self._value = value
self.offset = offset
@property
def value(self):
return self._value + self.offset
@value.setter
def value(self, value):
self._value = value |
value.setter对于常规任务来说是很好的,但是对于复合任务来说当然是坏的:
1 2 3 4 5 6 7 8 9
| >>> x = Foo(3, 2)
>>> x.value
5
>>> x.value = 2
>>> x.value
4
>>> x.value += 5
>>> x.value
11 |
所期望的行为是,与x.value = x.value + 5相反,x.value += 5应等同于x.value = x._value + 5。是否有任何方法可以(通过属性接口)完全在Foo类中实现这一点?
- 我建议不要从一开始就将神奇的数字行为添加到一个属性中,否则您将使用标准数学运算符破坏对称性(正如您注意到的那样)。定义offsetValue()使读者的意图更为明显;特别是,x.value += 5是否应该添加到_value或offset中?
- 在意识到setter的行为总体上是有缺陷的之前,我对if 'INPLACE_ADD' in [dis.opname[ord(op)] for op in inspect.stack()[1][0].f_code.co_code]:做了一些尝试。我用它工作了这么长时间,我没有想到要重新审视它。
- 如果没有那个偏移,它看起来工作正常。我同意Ezod的观点,它在尝试将添加的结果分配给计算属性时做了一些奇怪的事情。有了偏移,它会产生意想不到的结果。没有它就没有了。嗯。
@Ezod,即使使用描述符协议,也没有直接的方法来做你想要做的事情。这种value属性的行为完全破坏了+=操作符的语义。
- 我不确定我明白。它如何打破+=的语义?从外部看,它将完全按预期工作。"偏移"功能(在本例中很小,但在我的实际应用程序中涉及)对用户是隐藏的,但是用户仍然可以使用其类的所有功能(包括__iadd__)直观地访问和修改公开的值,而不会产生这些奇怪的效果。
- 进一步考虑,我想我明白你的意思了——x += y应该等同于x = x + y,这在我的例子中是不正确的。够公平的。
- 考虑一下:y=x.value;x.value+=5;y+=5;此时,程序员期望x==y。
重写__iadd__magic方法。
您需要这样做,因为+=意味着"取值并加5,然后将其设置为结果"。如果您想让它知道值不是真正的值,您需要更改+=运算符的语义。
- 在Foo中覆盖__iadd__将不起作用,因为我没有添加到Foo中,而是添加到它的成员中。即使我有权访问value的类,并且没有多态性,它也不知道Foo的内部,所以我也不能在那里覆盖__iadd__。
- __iadd__也只对可变类型调用,否则调用__add__。另外,尝试编写这样工作的语义,通过对传递的值进行子分类——不管做什么,最终都会递归。
我也遇到了同样的问题,用以下方法解决了它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Foo(object):
def __init__(self):
self._y = 0
@property
def y(self):
return self._y + 5
@y.setter
def y(self, value):
value -= 5
self._y = value
>>> f = Foo()
>>> f.y
5
>>> f.y += 1
>>> f._y
1
>>> f.y
6 |
这是一个解决方案吗?我忽略了什么吗?
The desired behavior is that x.value += 5 should be equivalent to
x.value = x._value + 5
为什么python应该知道属性是如何实现的(即,setter将其值精确地分配给_x?
property的协议允许您为一个名称分配不同的操作(get、set、delete)。这个协议并不关心这些行为的实现方式。
所以,如果Python对您的代码做一些假设,并尝试修改Straight Forward代码逻辑,那将是非常容易混淆的。
- 我不是建议Python应该知道这一点。更多的是沿着setter的路线,能够反省它是如何被称为(即通过=和+=等)的,并做出相应的行为。