Как реализовать iadd для свойства Python
Я пытаюсь создать свойство Python, где добавление на месте обрабатывается другим методом, чем получение значения, добавление другого значения и переназначение. Итак, для свойства x
на объекте o
,
o.x += 5
должны работать иначе, чем
o.x = o.x + 5
значение o.x
должно быть то же самое в конце, чтобы не путать ожидания людей, но я хочу, чтобы на месте добавить более эффективным. (На самом деле операция занимает много времени больше времени, чем простое сложение.)
моей первой мыслью было определить в классе
x = property(etc. etc.)
x.__iadd__ = my_iadd
но это вызывает AttributeError, предположительно, потому что property
осуществляет __slots__
?
моя следующая попытка использует объект дескриптора:
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
как вы можете видеть, специальный __iadd__
метод не вызывается. Мне трудно понять, почему это так, хотя я предполагаю, что объект __getattr__
как-то в обход его.
как могу я это сделать? Разве я не понимаю смысл дескрипторов? Мне нужен метакласс?
4 ответов
__iadd__
будет искать только значение, возвращаемое из __get__
. Вам нужно сделать __get__
(или свойство getter) возвращает объект (или прокси-объект) с __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
на +=
оператор в строке
o.x += 5
переведен на
o.x = o.x.__iadd__(5)
поиск атрибута на правой стороне переводится в
o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)
Как видите, __iadd__()
о возвращаемое значение поиска атрибутов, поэтому вам нужно реализовать __iadd__()
на возвращенном объекте.
чтобы вдохновить вас, здесь меньше, чем идеальное решение, которое является лучшим, что мне удалось придумать до сих пор:
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'
большим недостатком является то, что вы должны реализовать полный набор численных магических методов на IAddObj
Если вы хотите, чтобы свойство вело себя как число. Имея IAddObj
наследовать от int
похоже,не позволяет ему наследовать операторы.
почему не что-то вроде следующего примера. В основном идея заключается в том, чтобы класс Bar гарантировал, что сохраненное значение для свойства x всегда является объектом 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: расширенный пример, чтобы показать, как использовать его в качестве свойства. Сначала я немного неправильно понял проблему.