为什么集合对象存储为冻结集而列表对象存储为元组?

Why is a set object stored as a frozenset and a list object as a tuple?

我看到了一个博客 post,其中提到了 "Use func.__code__.co_consts to check all the constants defined in the function"

def func():
    return {1,2,3} 中的 1 个
func.__code__.co_consts
(None, 1, frozenset({1, 2, 3}))

为什么 return 是 frozenset

def func():
    return [1,2,3] 中的 1 个
func.__code__.co_consts
(None, 1, (1,2,3))

为什么 return 是 tuple 而不是列表?从 __code__.co_consts 编辑的每个对象 return 都是 不可变的 。为什么可变常量不可变?为什么 returned 元组的第一个元素总是 None

这是 Python Peephole optimizer

的结果

在"Optimizations"下,它说:

BUILD_LIST + COMPARE_OP(in/not in): convert list to tuple
BUILD_SET + COMPARE_OP(in/not in): convert set to frozenset 

有关详细信息,请参阅 here

"Python uses peephole optimization of your code by either pre-calculating constant expressions or transforming certain data structures"

尤其是关于“会员测试”的部分:

"What Python for membership tests is to transform mutable data structures to its inmutable version. Lists get transformed into tuples and sets into frozensets."

co_consts中的所有对象都是常量,即它们是不可变的。例如,您不应该能够附加到在源代码中显示为文字的列表,从而修改函数的行为。

编译器通常通过列出列表中出现的所有单个常量来表示列表文字:

>>> def f():
...     a = [1, 2, 3]
...     return 1 in a
... 
>>> f.__code__.co_consts
(None, 1, 2, 3)

查看这个函数的字节码可以看出,函数每次执行时都会在执行时建立一个列表:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               3 (3)
              6 BUILD_LIST               3
              8 STORE_FAST               0 (a)

  3          10 LOAD_CONST               1 (1)
             12 LOAD_FAST                0 (a)
             14 COMPARE_OP               6 (in)
             16 RETURN_VALUE

一般需要创建一个新的列表,因为函数可能会修改或者return字面量定义的列表,这种情况下每次执行函数都需要对新的列表对象进行操作.

不过,在其他情况下,创建新的列表对象是一种浪费。出于这个原因,Python 的窥孔优化器可以在已知安全的某些情况下用元组替换列表,或用 frozen_set 替换集合。一种这样的情况是列表或集合文字仅用于 x [not] in <list_literal> 形式的表达式中。另一种情况是在 for 循环中使用列表文字。

窥孔优化器非常简单。它一次只看一个表情。出于这个原因,它无法检测到此优化在我上面 f 的定义中是安全的,这在功能上等同于您的示例。