问题
I'm trying to create a Python property where in-place adding is handled by a different method than retrieving the value, adding another value and reassigning. So, for a property x
on an object o
,
o.x += 5
should work differently than
o.x = o.x + 5
The value of o.x
should be the same in the end, so as not to confuse people's expectations, but I want to make the in-place add more efficient. (In reality the operation takes a lot more time than simple addition.)
My first idea was to define, in the class,
x = property(etc. etc.)
x.__iadd__ = my_iadd
But this raises an AttributeError, presumably because property
implements __slots__
?
My next attempt uses a descriptor object:
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
As you can see, the special __iadd__
method is not called. I'm having trouble understanding why this is, although I surmise that the object's __getattr__
is somehow bypassing it.
How can I do this? Am I not getting the point of descriptors? Do I need a metaclass?
回答1:
__iadd__
will only be looked for on the value returned from __get__
. You need to make __get__
(or the property getter) return an object (or a proxy object) with __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
回答2:
The +=
operator in the line
o.x += 5
is translated to
o.x = o.x.__iadd__(5)
The attribute lookup on the right-hand side is translated to
o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)
As you can see, __iadd__()
is called on the return value of the attribute lookup, so you need to implement __iadd__()
on the returned object.
回答3:
To inspire you, here's a less-than-ideal solution which is the best I've managed to come up with so far:
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'
The big disadvantage being, that you have to implement the full complement of numerical magic methods on IAddObj
if you want the property to behave anything like a number. Having IAddObj
inherit from int
doesn't seem to let it inherit the operators, either.
回答4:
Why not something like the following example. Basically the idea is to let the Bar class ensure the stored value for property x is always a Foo object.
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: Extended example to show how to use it as a property. I first misunderstood the problem slightly.
来源:https://stackoverflow.com/questions/11987949/how-to-implement-iadd-for-a-python-property