Cómo implementar __iadd__ para una propiedad de Python

Estoy tratando de crear una propiedad de Python donde la adición in situ se maneja mediante un método diferente al de recuperar el valor, agregar otro valor y reasignar. Entonces, para una propiedad x en un objeto o ,

 ox += 5 

debería funcionar de manera diferente a

 ox = ox + 5 

El valor del ox debería ser el mismo al final, para no confundir las expectativas de la gente, pero quiero hacer que el agregado sea más eficiente. (En realidad, la operación lleva mucho más tiempo que una simple adición).

Mi primera idea fue definir, en la clase,

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

Pero esto genera un AttributeError, probablemente porque la property implementa __slots__ ?

Mi siguiente bash usa un objeto descriptor:

 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 ox 5 >>> ox = 10 >>> print ox 10 >>> ox += 5 # '__iadd__!' not printed >>> print ox 15 

Como puede ver, no se llama al método __iadd__ especial. Tengo problemas para entender por qué esto es así, aunque supongo que el __getattr__ del objeto de alguna manera lo está pasando por alto.

¿Cómo puedo hacer esto? ¿No estoy captando el punto de descriptores? ¿Necesito una metaclase?

__iadd__ solo se __iadd__ en el valor devuelto desde __get__ . __get__ hacer que __get__ (o el que __get__ la propiedad) devuelva un objeto (o un objeto proxy) con __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 

El operador += en la línea

 ox += 5 

se traduce a

 ox = ox__iadd__(5) 

El atributo de búsqueda en el lado derecho se traduce a

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

Como puede ver, se __iadd__() en el valor de retorno de la búsqueda de atributos, por lo que necesita implementar __iadd__() en el objeto devuelto.

Para inspirarte, aquí hay una solución menos que ideal que es la mejor que he logrado encontrar hasta ahora:

 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 ox 5 >>> ox = 10 >>> print ox 10 >>> ox += 5 __iadd__! >>> print ox 15 >>> print ox + 5 # doesn't work unless __add__ is also implemented TypeError: unsupported operand type(s) for +: 'IAddObj' and 'int' 

La gran desventaja es que tienes que implementar el complemento completo de métodos mágicos numéricos en IAddObj si quieres que la propiedad se comporte de forma similar a un número. Tener IAddObj heredado de int tampoco parece permitir que herede los operadores.

¿Por qué no algo como el siguiente ejemplo? Básicamente, la idea es dejar que la clase Bar asegure que el valor almacenado para la propiedad x sea siempre un objeto 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) 

EDITAR: Ejemplo extendido para mostrar cómo usarlo como una propiedad. Primero entendí mal el problema un poco.