2012-08-16 58 views
10

我试图创建一个Python属性,其中就地添加是通过不同的方法处理而不是检索值,添加另一个值并重新分配。因此,对于一个对象o上的属性x如何为Python属性实现__iadd__

o.x += 5 

应该工作不同于

o.x = o.x + 5 

o.x值应该是在年底相同,以免混淆人们的预期,但我想使就地添加更高效。 (在现实中,操作时间比简单的加法更多的时间。)

我的第一个想法是定义在类,

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

但由于property实现__slots__这引发了一个AttributeError,想必?

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

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__在某种程度上绕过了它。

我该怎么做?我没有得到描述符的重点吗?我需要一个元类吗?

回答

6

__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 
+0

我有点困惑这个代码片段。财产获得者不应该返回什么? – senderle 2012-08-16 14:48:22

+0

@senderle噢,哎呀。对subclassing int的问题感到困惑。谢谢! – ecatmur 2012-08-16 14:54:26

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上的数字魔术方法的补充。拥有IAddObj继承自int似乎也不让它继承运营商,无论是。

+0

你说“让IAddObj继承自'int'似乎并不让它继承运营商,”。这不完全正确。问题是,如果你不覆盖所有其他的操作符,那么'IAddObj(5)+ 5'的类型是'int',而不是'IAddObj',因为int是不可变的,所有操作总是返回新的int。但是,使用元类的确有一种解决方法。 [看到这个答案](http://stackoverflow.com/q/10771010/577088)。 – senderle 2012-08-16 14:14:23

+0

然而,更多地思考它,我相信ecatmur和muksie提供的解决方案将不那么复杂,更适合您的实际目标。 – senderle 2012-08-16 14:49:10

1

为什么不能像下面的例子。基本上这个想法是让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) 

编辑:扩展示例以显示如何使用它作为属性。我首先误解了这个问题。

4

在线路的+=操作者

o.x += 5 

被转换为

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

在右手侧上的属性查找被转换为

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

作为你可以n请参阅__iadd__()返回值上被调用的属性查找,因此您需要在返回的对象上实现__iadd__()

+0

这是不正确的。调用'o.x .__ iadd __(5)',或者如果'o.x'没有'__iadd__'方法,则调用'o.x = o.x + 5'。也就是说,'o.x = o.x .__ iadd __(5)'从不使用,因为'__iadd__'不会返回任何东西。 – 2013-08-28 12:59:48

+0

@MarkLodato请在将它们喊出来之前验证您的声明。答案完全如我所料。试试看。 – glglgl 2013-12-10 22:41:55

+0

@MarkLodato看这里:http://codepad.org/wbURpQJT它显示了'__iadd __()'在执行'+ ='时的方式(能够分辨左手名称的全新内容),并且它也显示什么'列表.__ iadd __()'返回:它的'self'。 – glglgl 2013-12-10 22:47:24