6

我试图了解使用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)

有了这些工具,让我们开始潜水吧:

>>> 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__?)
4

2 回答 2

11

在第一个示例中,您没有将+=运算符应用于字典。您将它应用于存储在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

m在全局命名空间中没有额外的名称。

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

这记录在增强的分配参考文档中

扩充赋值计算目标(与普通赋值语句不同,不能解包)和表达式列表,对两个操作数执行特定于赋值类型的二元运算,并将结果分配给原始目标。

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

于 2014-03-21T21:40:58.937 回答
0

因为,如docs中所述,__iadd__可能会就地执行操作,但结果将是 self 或新对象,因此__setitem__被调用。

于 2014-03-21T21:41:49.597 回答