objects español decorators classes and python properties descriptor magic-methods

español - Cómo implementar__iadd__ para una propiedad de Python



python decorator (4)

Intento crear una propiedad de Python en la que la adición in situ se maneje con un método diferente al de recuperar el valor, agregar otro valor y reasignar. Entonces, para una propiedad x en un objeto o ,

o.x += 5

debería funcionar de manera diferente

o.x = o.x + 5

El valor de ox debe ser el mismo al final, para no confundir las expectativas de las personas, pero quiero que el agregado en el lugar sea más eficiente. (En realidad, la operación lleva mucho más tiempo que la simple adición).

Mi primera idea fue definir, en la clase,

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

Pero esto plantea un AttributeError, presumiblemente porque la property implementa __slots__ ?

Mi próximo intento 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 o.x 5 >>> o.x = 10 >>> print o.x 10 >>> o.x += 5 # ''__iadd__!'' not printed >>> print o.x 15

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

¿Cómo puedo hacer esto? ¿No entiendo el sentido de los descriptores? ¿Necesito una metaclase?


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

EDIT: ejemplo extendido para mostrar cómo usarlo como una propiedad. Primero entendí mal el problema.


El operador += en la línea

o.x += 5

se traduce a

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

La búsqueda de atributos en el lado derecho se traduce a

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

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


Para inspirarte, aquí hay una solución menos que ideal, que es la mejor con la que he podido llegar 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 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''

La gran desventaja es que debe implementar el complemento completo de los métodos de magia numérica en IAddObj si desea que la propiedad se comporte como un número. Tener IAddObj heredando de int no parece permitir que herede los operadores, tampoco.


__iadd__ solo se __iadd__ en el valor devuelto por __get__ . Debes hacer que __get__ (o el getter de 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