Python字典“plus equal”行为

2024-05-16 21:12:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解使用d[key] += diff更新python字典背后的确切机制。我有一些helper类来跟踪魔法方法调用:

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

Tags: selfaddreturnvaluedefargsvalsdict
2条回答

因为,正如在docs中指定的,__iadd__可能就地执行操作,但结果将是self或新对象,因此调用__setitem__

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

这是在augmented assignment reference documentation下记录的:

An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target.

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

相关问题 更多 >