我正在尝试创建一个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__
以某种方式绕过它。
我该怎么做?我没有得到描述符的意思吗?我需要一个元类吗?
答案 0 :(得分:7)
__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
答案 1 :(得分:4)
行中的+=
运算符
o.x += 5
被翻译为
o.x = o.x.__iadd__(5)
右侧的属性查找转换为
o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)
如您所见,__iadd__()
在属性查找的返回值上被调用,因此您需要在返回的对象上实现__iadd__()
。
答案 2 :(得分:1)
为了激励你,这是一个不太理想的解决方案,这是迄今为止我设法达到的最佳解决方案:
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
似乎也不允许它继承运算符。
答案 3 :(得分:1)
为什么不能像以下示例那样。基本上,我们的想法是让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)
编辑:展示如何将其用作属性的扩展示例。我首先误解了这个问题。