【问题标题】:How to implement __iadd__ for a Python property如何为 Python 属性实现 __iadd__
【发布时间】:2012-08-12 20:22:28
【问题描述】:

我正在尝试创建一个 Python 属性,其中就地添加是由与检索值、添加另一个值和重新分配不同的方法处理的。因此,对于对象 o 上的属性 x

o.x += 5

应该不同于

o.x = o.x + 5

o.x的值最后应该是一样的,以免混淆人们的期望,但是我想让in-place add更有效率。 (实际上这个操作比简单的加法要花更多的时间。)

我的第一个想法是在课堂上定义,

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__ 以某种方式绕过了它。

我该怎么做?我没有得到描述符的意义吗?我需要一个元类吗?

【问题讨论】:

    标签: python properties descriptor magic-methods


    【解决方案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 上实现完整的数字魔术方法。让IAddObjint 继承似乎也不会让它继承运算符。

    【讨论】:

    • 您说“让 IAddObj 从 int 继承似乎也不会让它继承运算符。”这并不完全正确。问题是,如果不覆盖所有其他运算符,那么IAddObj(5) + 5 的类型是int,而不是IAddObj,因为int 是不可变的,并且所有操作总是返回新的int。但是,确实有一种使用元类的方法。 See this answer.
    • 但是,仔细想想,我相信 ecatmur 和 muksie 提供的解决方案会更简单,更适合您的实际目标。
    【解决方案2】:

    为什么不像下面的例子。基本上这个想法是让 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)
    

    编辑:扩展示例以显示如何将其用作属性。我首先稍微误解了这个问题。

    【讨论】:

      【解决方案3】:

      __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
      

      【讨论】:

      • 我对这段代码 sn-p 有点困惑。属性 getter 不应该返回一些东西吗?
      • @senderle 哦,是的,哎呀。对子类化int 的问题有点困惑。谢谢!
      【解决方案4】:

      行中的+= 运算符

      o.x += 5
      

      被翻译成

      o.x = o.x.__iadd__(5)
      

      右侧的属性查找被翻译成

      o.x = IAddProp.__get__(TestObj2.x, o, TestObj2).__iadd__(5)
      

      可以看到,__iadd__()是在属性查找的返回值上调用的,所以需要在返回的对象上实现__iadd__()

      【讨论】:

      • 这是不正确的。要么调用o.x.__iadd__(5),要么如果o.x 没有并且__iadd__ 方法,则调用o.x = o.x + 5。也就是说,永远不会使用o.x = o.x.__iadd__(5),因为__iadd__ 不会返回任何内容。
      • @MarkLodato 请先验证您的声明,然后再大声疾呼。它与答案中的weitten完全相同。试试看。
      • @MarkLodato 看这里:codepad.org/wbURpQJT 它显示了在执行 += 时如何调用 __iadd__()(能够将一些全新的东西添加到左侧名称),它还显示了 @ 987654337@ 返回:self
      • @glglgl,你是对的。对于那个很抱歉。参考:docs.python.org/3/reference/…
      猜你喜欢
      • 2012-08-03
      • 2018-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-24
      • 2013-11-18
      • 2020-02-09
      • 2021-11-08
      相关资源
      最近更新 更多