我正在尝试了解使用d[key] += diff更新python字典的确切机制。我有一些帮助程序类来跟踪魔术方法调用:

class sdict(dict):
    def __setitem__(self, *args, **kargs):
        print "sdict.__setitem__"
        return super(sdict, self).__setitem__(*args, **kargs)
    def __delitem__(self, *args, **kargs):
        print "sdict.__delitem__"
        return super(sdict, self).__delitem__(*args, **kargs)
    def __getitem__(self, *args, **kargs):
        print "sdict.__getitem__"
        return super(sdict, self).__getitem__(*args, **kargs)
    def __iadd__(self, *args, **kargs):
        print "sdict.__iadd__"
        return super(sdict, self).__iadd__(*args, **kargs)
    def __add__(self, *args, **kargs):
        print "sdict.__add__"
        return super(sdict, self).__add__(*args, **kargs)

class mutable(object):
    def __init__(self, val=0):
        self.value = val
    def __iadd__(self, val):
        print "mutable.__iadd__"
        self.value = self.value + val
        return self
    def __add__(self, val):
        print "mutable.__add__"
        return mutable(self.value + val)

使用这些工具,让我们开始潜水:
>>> d = sdict()
>>> d["a"] = 0
sdict.__setitem__
>>> d["a"] += 1
sdict.__getitem__
sdict.__setitem__
>>> d["a"]
sdict.__getitem__
1

我们在此处看不到任何__iadd__操作被调用,这是有道理的,因为左侧表达式d["a"]返回的整数不实现__iadd__方法。我们确实看到python神奇地将+=运算符转换为__getitem____setitem__调用。

继续:
>>> d["m"] = mutable()
sdict.__setitem__
>>> d["m"] += 1
sdict.__getitem__
mutable.__iadd__
sdict.__setitem__
>>> d["m"]
sdict.__getitem__
<__main__.mutable object at 0x106c4b710>

在此,+=运算符成功调用了__iadd__方法。好像+=运算符实际上已被使用两次:
  • 一次将魔术转换为__getitem____setitem__会调用
  • 第二次调用__iadd__

  • 我需要帮助的地方如下:
  • +=运算符转换为__getitem____setitem__调用的确切技术机制是什么?
  • 在第二个示例中,为什么+=运算符使用了两次? python不会将语句转换为d["m"] = d["m"] + 1(在这种情况下,我们不会看到调用__add__而不是__iadd__吗?)
  • 最佳答案

    在第一个示例中,您没有将+=运算符应用于字典。您将其应用于存储在d['a']键中的值,这是一个完全不同的对象。

    换句话说,Python将检索d['m'](一个__getitem__调用),将+=运算符应用于该运算符,然后将该表达式的结果设置回d['m'](__setitem__调用)。
    __iadd__方法要么返回就地变异的self,要么返回一个新对象,但是Python无法确定该方法返回了什么。因此,它必须始终调用d.__setitem__('m', <return_value_from_d['m'].__iadd__(1)>)

    如果您这样做,则会发生完全相同的事情:

    m = d['m']
    m += 1
    d['m'] = m
    

    但在全局 namespace 中没有多余的名称m

    如果mutable()实例未存储在字典中,而是存储在全局 namespace 中,则会发生完全相同的事件序列,但直接在globals()字典上发生,并且您将看不到__getitem____setitem__调用。

    这记录在augmented assignment reference documentation下:



    其中d['m']是目标;在这里评估目标涉及__getitem__,将结果分配回原始目标将调用__setitem__

    10-07 19:39
    查看更多