如何使只读属性可变?

时间:2016-07-22 16:46:12

标签: python python-2.7 properties new-style-class augmented-assignment

我有两个班,一个是"就地操作员"覆盖(比如+=)和另一个通过@property公开第一个实例。 (注意:这是大大从我的实际代码简化到最小化再现问题。)

class MyValue(object):
    def __init__(self, value):
        self.value = value

    def __iadd__(self, other):
        self.value += other
        return self

    def __repr__(self):
        return str(self.value)

class MyOwner(object):
    def __init__(self):
        self._what = MyValue(40)

    @property
    def what(self):
        return self._what

现在,当我尝试在公开的属性上使用该运算符时:

>>> owner = MyOwner()
>>> owner.what += 2
AttributeError: can't set attribute

我发现这是预期的,因为它试图在owner上设置属性。 是否有某种方法可以阻止属性设置为新对象,同时仍允许我(就地)修改其背后的对象,或者这是只是语言的一个怪癖?

(另请参阅this question,但我尝试采用其他方式,最好是,而不是还原为旧式类,因为最终我希望它能够与Python 3一起使用。)

与此同时,我用一种做同样事情的方法解决了这个问题。

class MyValue(object):
    # ... 

    def add(self, other):
        self.value += other

>>> owner = MyOwner()
>>> owner.what.add(2)
>>> print(owner.what)
42

1 个答案:

答案 0 :(得分:5)

这是语言的怪癖; object += value操作转换为:

object = object.__iadd__(value)

这是必要的,因为并非所有对象都是可变的。你的是,并且正确地返回self,导致上述操作的赋值部分的虚拟无操作。

在您的情况下,相关的object也是一个属性,因此执行以下操作:

owner.what = owner.what.__iadd__(2)

除了避免在左侧引用object.what(如tmp = owner.what; tmp += 2)之外,还有一种方法可以干净利落地处理它。

您可以轻松检测到属性的分配涉及同一个对象并对其进行门控:

class MyOwner(object):
    def __init__(self):
        self._what = MyValue(40)

    @property
    def what(self):
        return self._what

    @what.setter
    def what(self, newwhat):
        if newwhat is not self._what:
            raise AttributeError("can't set attribute")
        # ignore the remainder; the object is still the same
        # object *anyway*, so no actual assignment is needed

演示:

>>> owner = MyOwner()
>>> owner.what
40
>>> owner.what = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 24, in what
AttributeError: can't set attribute
>>> owner.what += 2
>>> owner.what
42