Python - 多重赋值语句有优缺点吗?

1 投票
2 回答
818 浏览
提问于 2025-04-17 17:22

在这里,我看到一个关于递归字典的解决方案,链接在这里。里面有一个Python的写法:

class RecursiveDict(dict):
    """Implementation of perl's autovivification feature."""
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

关于这一行代码value = self[key] = type(self)():这行代码相比于下面的代码,有什么优点或缺点吗?还是说这只是语法上的不同?

self[key] = type(self)()
value = self[key]

2 个回答

1

这只是语法问题。有时候这样写代码更容易读懂,有时候则不然。

那么到底发生了什么呢?另一个回答让我产生了好奇:我在下面评论说编译器可能使用了“寄存器或变量”,结果确实是这样。我们来看看一个简单函数的反编译:

>>> import dis
>>> def f():
...     a = b = c = 1
...     return a
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              3 DUP_TOP             
              4 STORE_FAST               0 (a)
              7 DUP_TOP             
              8 STORE_FAST               1 (b)
             11 STORE_FAST               2 (c)

  3          14 LOAD_FAST                0 (a)
             17 RETURN_VALUE        

最右边的值(在我的例子中是1,实际情况是type(self)())被加载到栈上(可以理解为“局部变量”),然后在栈上复制一份(在基于栈的虚拟机中,操作会消耗栈上的值,所以如果你想“保留”一个值,就需要多复制几份),接着进行赋值,然后再复制,再赋值,依此类推。换句话说,转换回Python代码大概是这样的:

def f():
    t = 1
    a = t
    b = t
    c = t
    return a

编译器甚至保持了从左到右的赋值顺序(a, b, c)。

4

这主要是语法上的简化,但并不是你列出的那些语句;相反,它等价于

value = type(self)()
self[key] = value

要查看区别,可以在你的Python提示符下输入以下内容:

>>> class FakeDict(object):
...  def __setitem__(self, k, v):
...   pass
...  def __getitem__(self, k):
...   raise KeyError("boom!")
... 
>>> d = FakeDict()
>>> x = d[1] = 42
>>> d[1] = 42
>>> x = d[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getitem__
KeyError: boom!

当然,如果你的 dict 使用得当,这个区别就不大,但

self[key] = type(self)()
value = self[key]

在第二行中确实会多做一次 dict 的查找,所以在循环中使用简写可能会提高性能。

一般情况下,这其实比我上面说的要复杂一点。简写的赋值实际上等价于

__some_temporary = type(self)()
value = __some_temporary
self[key] = __some_temporary

而简化的形式 value = type(self)(); self[key] = value 之所以成立,仅仅是因为 value 是一个简单的局部变量。如果 value 被替换成像 container[key] 这样的表达式,而这个表达式可能会失败,那么这种等价关系就不再成立了。

撰写回答