Как реализовать __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__
каким-то образом обходит его.
Как я могу это сделать? Разве я не получаю дескрипторов? Мне нужен метакласс?
Ответы
Ответ 1
__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
Ответ 2
Оператор +=
в строке
o.x += 5
переводится на
o.x = o.x.__iadd__(5)
Поиск атрибута в правой части преобразуется в
o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)
Как вы можете видеть, __iadd__()
вызывается для возвращаемого значения поиска атрибута, поэтому вам нужно реализовать __iadd__()
для возвращаемого объекта.
Ответ 3
Чтобы вдохновить вас, здесь решение, меньшее, чем идеальное, лучшее, что мне удалось придумать до сих пор:
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
, похоже, не позволяет ему наследовать операторы.
Ответ 4
Почему бы не что-то вроде следующего примера. В основном идея состоит в том, чтобы позволить классу 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: расширенный пример, чтобы показать, как использовать его как свойство. Я сначала неправильно понял проблему.