2012-08-16 57 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