Python字典"加 - 平等"行为

moa*_*tra 6 python dictionary operators magic-methods mutators

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

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)
Run Code Online (Sandbox Code Playgroud)

有了这些工具,让我们去潜水:

>>> d = sdict()
>>> d["a"] = 0
sdict.__setitem__
>>> d["a"] += 1
sdict.__getitem__
sdict.__setitem__
>>> d["a"]
sdict.__getitem__
1
Run Code Online (Sandbox Code Playgroud)

我们没有看到__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>
Run Code Online (Sandbox Code Playgroud)

这里+=操作员成功调用了一个__iadd__方法.看起来+=操作符实际上被使用了两次:

  • 一次用于魔术翻译__getitem____setitem__调用
  • 第二次__iadd__通话.

我需要帮助的地方如下:

  • +=运营商转换为电话__getitem____setitem__电话的确切技术机制是什么?
  • 在第二个例子中,为什么+=操作员使用了两次?不是python将语句翻译成d["m"] = d["m"] + 1 (在这种情况下我们不会看到__add__被调用而不是__iadd__?)

Mar*_*ers 10

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

换句话说,Python将检索d['m'](__getitem__调用),将+=运算符应用于该运算符,然后将该表达式的结果设置回d['m'](__setitem__调用).

__iadd__方法要么self就地返回mutate,要么返回一个新对象,但Python无法确定该方法返回的内容.所以它必须d.__setitem__('m', <return_value_from_d['m'].__iadd__(1)>)始终打电话.

如果你这样做会发生同样的事情:

m = d['m']
m += 1
d['m'] = m
Run Code Online (Sandbox Code Playgroud)

但没有m全局命名空间中的额外名称.

如果mutable()实例没有存储在字典中而是存储在全局命名空间中,则会发生完全相同的事件序列,但是直接在globals()字典上,您将无法查看__getitem____setitem__调用.

这在扩充的分配参考文档中记录:

增强赋值评估目标(与正常赋值语句不同,它不能是解包)和表达式列表,执行特定于两个操作数上的赋值类型的二进制运算,并将结果赋给原始目标.

d['m']目标在哪里; 在此处评估目标涉及__getitem__,将结果分配回原始目标调用__setitem__.