Python 使用嵌套的特殊可调用类型会引发TypeError:。。。对象不支持项分配

Python 使用嵌套的特殊可调用类型会引发TypeError:。。。对象不支持项分配,python,python-3.x,Python,Python 3.x,考虑下面的类 class Spam: def __init__(self): from collections import defaultdict self.eggs = defaultdict(list) def __getitem__(self, index): class AdderHelper: def __init__(self, eggs): self.eggs

考虑下面的类

class Spam:
    def __init__(self):
        from collections import defaultdict
        self.eggs = defaultdict(list)
    def __getitem__(self, index):
        class AdderHelper:
            def __init__(self, eggs):
                self.eggs = eggs
            def __iadd__(self, egg):
                self.eggs.append(egg)
                return self
        return AdderHelper(self.eggs[index])
为了提供一个索引就地add操作符,我定义了一个Helper类(类似于迭代器模式),并提供了要在该类的实例上调用的下一级操作符

例如,我打算提供以下操作员行为

spam = Spam()
spam[0] += 1
不幸的是,Python不喜欢它,并且抱怨

Traceback (most recent call last):

  File "<ipython-input-82-ee0bba1041e3>", line 1, in <module>
    spam[0] = 1

TypeError: 'Spam' object does not support item assignment
看起来,我根本不理解这种行为 然而,对于就地添加,它确实调用了setitem,克服它的唯一方法是重写setitem函数,如中所示

 def __setitem__(self, index, value):
    obj = self.__getitem__(index)
    obj += value
    return self
注意

Q1:@vaultah spam[0]+=1基本上是spam[0]=spam[0]+1

我不能;找不到任何文档支持python将就地运算符视为二进制运算,第一个操作数为self。就连拆卸也不这么说

def foo(spam):
    spam[0] += 1
dis.dis(foo)
  2           0 LOAD_FAST                0 (spam)
              2 LOAD_CONST               1 (0)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_CONST               2 (1)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

+=
是一个扩展赋值运算符,它调用LHS的
\uuuIdd\uIdd
方法(如果存在),然后将其返回值赋值给LHS:

例如,如果x是一个类的实例,该类的 方法,
x+=y
相当于
x=x.。\uuuu iadd\uuuu(y)

()

因此

相当于

spam = Spam()
spam[0] = spam[0].__iadd__(1)
虽然
spam[0]
成功是因为实现了
\uuuu getitem\uuuuu
,但对
spam[0]
的后续分配不会成功,因为它需要
\uuuuu setitem\uuuuu
才能工作。同时,
spam[0].eggs
列表也会更新,因为
\uuuuuidd\uuuu
按预期工作:

In [4]: spam[0].eggs
Out[4]: [1]

有关常见问题,请参阅。

+=
是一个增强赋值运算符,它调用LHS的
\uuuu iadd\uuuu
方法(如果存在),然后将其返回值赋值给LHS:

例如,如果x是一个类的实例,该类的 方法,
x+=y
相当于
x=x.。\uuuu iadd\uuuu(y)

()

因此

相当于

spam = Spam()
spam[0] = spam[0].__iadd__(1)
虽然
spam[0]
成功是因为实现了
\uuuu getitem\uuuuu
,但对
spam[0]
的后续分配不会成功,因为它需要
\uuuuu setitem\uuuuu
才能工作。同时,
spam[0].eggs
列表也会更新,因为
\uuuuuidd\uuuu
按预期工作:

In [4]: spam[0].eggs
Out[4]: [1]

请参阅相关常见问题解答。

spam[0]=1
调用
\uuuuu setitem\uuuuuuuuuuu
,而不是
\uuuuuuuu getitem\uuuuuuuuuuuuu
@vaultah:很抱歉,出现了一个印刷错误,我的观点仍然站得住脚
spam[0]+=1
基本上就是
spam[0]=spam[0]+1
@vaultah。我不能;我找不到任何引用,它说python将把一个就地运算符当作一个二进制运算,第一个操作数是self。甚至反汇编也会说:是的,对不起,我跳过了
\uuuu iadd\uuuuuu
部分
spam[0]=1
调用
\uuuuu setitem\uuuuuuuu
@vaultah,而不是
\uuu getitem\uuuuuuuu
:对不起,有一个印刷错误,我的观点仍然站得住脚
spam[0]+=1
基本上就是
spam[0]=spam[0]+1
@vaultah。我不能;我找不到任何引用,它说python将把一个就地运算符当作一个二进制运算,第一个操作数是self。甚至反汇编也会说:是的,对不起,我跳过了
\uu\uud\uu
部分