【问题标题】:Why is a set object stored as a frozenset and a list object as a tuple?为什么集合对象存储为frozenset,列表对象存储为元组?
【发布时间】:2020-06-26 16:26:03
【问题描述】:

我看到一篇博文提到“使用func.__code__.co_consts 检查函数中定义的所有常量”

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

为什么返回frozenset

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

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

【问题讨论】:

  • 不可变是不可改变的。这就是为什么!
  • 也许线索在 consts 中?
  • @quamrana 我是初学者。介意给我详细解释一下吗?或我可以阅读它的链接。
  • 在 C/C++ 中有一个关键字 const 可以使数据看起来不可变。 (en.cppreference.com/w/cpp/language/cv) 在 python 中,它是通过拥有没有变异方法的类型来完成的。
  • @quamrana 感谢您的洞察力。这很有帮助。 ;) 我会查看附加的链接。

标签: python list set python-internals


【解决方案1】:

这是 Python Peephole optimizer 的结果

在“优化”下,它说:

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

更多信息请见here

“Python 通过预先计算常量表达式或转换某些数据结构来优化代码”

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

“用于成员资格测试的 Python 是将可变数据结构转换为其不可变版本。列表转换为元组,集合转换为冻结集。”

【讨论】:

  • 感谢您的回答;)我会查看提供的所有链接。
【解决方案2】:

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

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

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

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

【讨论】:

  • 感谢您的回答。我不明白这部分 因为该函数可能会返回列表文字,在这种情况下,每次调用它时它都应该返回一个新列表。在其他情况下,这种行为是浪费的,尽管。你介意更详细地向我解释一下吗?非常感激。 +1
  • 如果函数定义为def f(): return [1, 2, 3],我们需要在每次执行函数时创建一个新列表,因为调用f()会返回一个新的每次都列出而不是相同的列表对象。所以在这种情况下,代码需要在函数执行期间构造一个新的列表对象。这种行为(实际上是在每次执行函数时构建一个新列表),但在其他一些情况下并不真正需要,例如当列表仅用于成员资格测试或对其进行迭代时。
  • @Ch3steR 在这些情况下,相当于使用一个常量元组而不是一个新构造的列表对象,并且使用元组效率更高,因此窥孔优化器应用了这种转换。
  • 谢谢!现在这对我来说很有意义。感谢您抽出时间进行解释。
猜你喜欢
  • 2016-05-08
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2022-12-06
相关资源
最近更新 更多