15

我正在尝试创建一个 Python 属性,其中就地添加由与检索值、添加另一个值和重新分配不同的方法处理。因此,对于x对象上的属性o

o.x += 5

应该不同于

o.x = o.x + 5

最终的值o.x应该是一样的,以免混淆人们的期望,但我想让就地添加更高效。(实际上,该操作比简单的加法要花费更多的时间。)

我的第一个想法是在课堂上定义,

x = property(etc. etc.)
x.__iadd__ = my_iadd

但这会引发 AttributeError,大概是因为propertyimplements __slots__?

我的下一次尝试使用描述符对象:

class IAddProp(object):
    def __init__(self):
        self._val = 5

    def __get__(self, obj, type=None):
        return self._val

    def __set__(self, obj, value):
        self._val = value

    def __iadd__(self, value):
        print '__iadd__!'
        self._val += value
        return self

class TestObj(object):
    x = IAddProp()
    #x.__iadd__ = IAddProp.__iadd__  # doesn't help

>>> o = TestObj()
>>> print o.x
5
>>> o.x = 10
>>> print o.x
10
>>> o.x += 5  # '__iadd__!' not printed
>>> print o.x
15

如您所见,__iadd__未调用特殊方法。我无法理解为什么会这样,尽管我推测该对象__getattr__以某种方式绕过它。

我怎样才能做到这一点?我没有得到描述符的意义吗?我需要一个元类吗?

4

4 回答 4

8

__iadd__只会在从__get__. 您需要使__get__(或属性获取器)返回一个对象(或代理对象)__iadd__

@property
def x(self):
    proxy = IProxy(self._x)
    proxy.parent = self
    return proxy

class IProxy(int, object):
    def __iadd__(self, val):
        self.parent.iadd_x(val)
        return self.parent.x
于 2012-08-16T13:20:20.320 回答
6

+=线路中的操作员

o.x += 5

被翻译成

o.x = o.x.__iadd__(5)

右侧的属性查找被转换为

o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)

可以看到,是在属性查找的返回值__iadd__()上调用的,所以需要在返回的对象上实现。__iadd__()

于 2012-08-16T13:40:17.727 回答
2

为什么不像下面的例子。基本上,这个想法是让 Bar 类确保属性 x 的存储值始终是 Foo 对象。

class Foo(object):
    def __init__(self, val=0):
        print 'init'
        self._val = val

    def __add__(self, x):
        print 'add'
        return Foo(self._val + x)

    def __iadd__(self, x):
        print 'iadd'
        self._val += x
        return self

    def __unicode__(self):
        return unicode(self._val)

class Bar(object):
    def __init__(self):
        self._x = Foo()

    def getx(self):
        print 'getx'
        return self._x

    def setx(self, val):
        if not isinstance(val, Foo):
            val = Foo(val)
        print 'setx'
        self._x = val

    x = property(getx, setx)

obj = Bar()
print unicode(obj.x)
obj.x += 5
obj.x = obj.x + 6
print unicode(obj.x)

编辑:扩展示例以显示如何将其用作属性。我首先稍微误解了这个问题。

于 2012-08-16T13:19:32.973 回答
1

为了激发您的灵感,这里有一个不太理想的解决方案,这是我迄今为止设法提出的最好的解决方案:

class IAddObj(object):
    def __init__(self):
        self._val = 5

    def __str__(self):
        return str(self._val)

    def __iadd__(self, value):
        print '__iadd__!'
        self._val += value
        return self._val

class TestObj2(object):
    def __init__(self):
        self._x = IAddObj()

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x._val = value

>>> o = TestObj2()
>>> print o.x
5
>>> o.x = 10
>>> print o.x
10
>>> o.x += 5
__iadd__!
>>> print o.x
15
>>> print o.x + 5  # doesn't work unless __add__ is also implemented
TypeError: unsupported operand type(s) for +: 'IAddObj' and 'int'

IAddObj最大的缺点是,如果您希望属性的行为类似于数字,则必须实现完整的数字魔术方法。继承IAddObjint似乎也不会让它继承运算符。

于 2012-08-16T13:18:44.487 回答