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