为什么我的代码在使用__iadd__时会在包含项中生成__setitem__错误?

4 投票
2 回答
1269 浏览
提问于 2025-04-18 14:10

有没有人能告诉我,为什么这段代码会抱怨说容器里没有 __setitem__ 呢?我以为只需要在容器里定义 __getitem__ 来获取项目,然后用 __iadd__ 来设置值就可以了,不明白为什么它还期待有 __setitem__

class Item:
    def __init__(self):
        pass

    def __iadd__(self, value):
        print 'added: ' + value
        return self


class Container:

    def __init__(self):
        self.__items = {
            'foo': Item()
        }

    def __getitem__(self, name):
        return self.__items[name]

if __name__ == '__main__':

    # works!
    con = Container()
    item = con['foo']
    item += 'works!'

    # wtf?
    con['foo'] += "what's going on?"

    # output:

    # added: works!
    # added: what's going on?
    # Traceback (most recent call last):
    #   File "foo.py", line 27, in <module>
    #     con['foo'] += "what's going on?"
    # AttributeError: Container instance has no attribute '__setitem__'

2 个回答

3

来猜猜这个问题:如果你对一个不可变类型,比如一个 int,使用 += 会发生什么?你能理解为什么对从 [] 返回的值调用 += 也需要调用 __setitem__ 吗?

关键在于理解,Python 中任何包含 = 的操作符都涉及到赋值,因为它需要以预期的方式处理不可变类型。

为了让这个更清楚,考虑以下例子:

>>> a = 10*10**9
>>> b = a
>>> a is b
True
>>> a += 1
>>> a is b
False

很明显,a 被赋值为一个新值。因此,当你对 __getitem__ 返回的值执行 += 时,也会发生同样的事情。

不幸的是,Python 的文档在说明这会发生什么方面做得非常糟糕,但正如其他答案通过反编译所指出的那样,这确实会发生,并且必须这样做,以正确处理存储在可变容器中的不可变类型。

3

基本上,

con['foo'] += "what's going on?"

编译后会变成类似这样的东西:

item = con['foo']
item += "what's going on?"
conf['foo'] = item

你可以看到,反编译这个代码,得到的结果大概是:

  2           0 LOAD_GLOBAL              0 (con)
              3 LOAD_CONST               1 ('foo')
              6 DUP_TOPX                 2
              9 BINARY_SUBSCR       
             10 LOAD_CONST               2 ("what's going on?")
             13 INPLACE_ADD         
             14 ROT_THREE           
             15 STORE_SUBSCR        

撰写回答