2012-08-16 15 views
10

Estoy intentando 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. Así, para una propiedad x en un objeto o,Cómo implementar __iadd__ para una propiedad de Python

o.x += 5 

deben trabajar de manera diferente que

o.x = o.x + 5 

El valor de o.x debe ser el mismo que al final, a fin de no confundir a las expectativas de las personas, pero Quiero hacer que el add in situ sea más eficiente. (En realidad, la operación lleva bastante más tiempo que una simple suma.)

Mi primera idea era definir, en la clase,

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

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

Mi siguiente intento se utiliza 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 se puede ver, el método especial __iadd__ no se llama. Tengo problemas para entender por qué ocurre esto, aunque supongo que el objeto __getattr__ de alguna manera lo pasa por alto.

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

Respuesta

6

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

Estoy un poco confundido por este fragmento de código. ¿No debería el comprador obtener algo? – senderle

+0

@senderle oh sí, oops. Tengo un poco confuso con los problemas de subclases 'int'. ¡Gracias! – ecatmur

1

Para inspirarte, aquí hay un menos-que-ideales solución que es el mejor que he conseguido llegar a la medida:

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 de ser, que hay que poner en práctica la plena complemento de métodos mágicos numéricos en IAddObj si desea que la propiedad se comporte como un número. Tener IAddObj heredar de int no parece permitir que herede los operadores, tampoco.

+0

Usted dice "Tener IAddObj heredando de' int' no parece permitir que herede los operadores tampoco ". Eso no es del todo cierto. El problema es que si no anulas a todos los otros operadores, entonces el tipo de 'IAddObj (5) + 5' es' int', no 'IAddObj', porque los ints son inmutables, y todas las operaciones siempre devuelven nuevas entradas. Sin embargo, de hecho hay una forma de evitar esto usando metaclases. [Ver esta respuesta] (http://stackoverflow.com/q/10771010/577088). – senderle

+0

Sin embargo, pensando en ello más, creo que ecatmur y muksie ofrecen soluciones que serán menos complejas y más adecuadas para sus objetivos reales. – senderle

1

¿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) 

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

4

El operador += en la línea de

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 tu ca n see, se llama al __iadd__() en el valor de retorno de la búsqueda de atributos, por lo que debe implementar __iadd__() en el objeto devuelto.

+0

Esto no es correcto. Se llama a 'o.x .__ iadd __ (5)', o si 'o.x' no tiene y' __iadd__' método, 'o.x = o.x + 5' se llama. Es decir, 'o.x = o.x .__ iadd __ (5)' nunca se usa, porque '__iadd__' no devuelve nada. –

+0

@MarkLodato Por favor, verifique sus reclamos antes de gritarlos. Es exactamente como se indica en la respuesta. Pruébalo. – glglgl

+0

@MarkLodato Mira aquí: http://codepad.org/wbURpQJT Muestra cómo se llama a '__iadd__()' al hacer '+ =' (capacidad para asignar algo completamente nuevo al nombre de la mano izquierda), y también se muestra what 'list .__ iadd __()' devuelve: its 'self'. – glglgl

Cuestiones relacionadas