为什么 [0] 是一个不同的函数而 0 不是?

Why is [0] a different function but 0 isn't?

我检查了 .__code__ 个对象的两个函数,我认为不同,但发现它们是相同的,用于各种表达式。如果代码对象相同,据我所知,它们会编译为相同的字节码,因此是 "same" 函数。

下面的

Table 是在 ; pass 之前插入的东西,这使得 g 有不同的 __code__。由于 f 是一个 "do nothing" 函数,这表明 "same" 到 从不执行 下的所有内容,包括长算术。此外,元组是 "same",但列表和字符串是 "diff" - 因此我们可以得出结论,涉及 不可变文字的 未分配 表达式 未被评估。但是还有 1/0,这可能是由于引发异常而导致的 "exception" - 那么 10**9910**9 呢? 10**99 不会引发异常,可以分配。

我无法从剖析中看出多少; "same" 和 "diff" 的执行时间都无法区分。然而,当它们可以区分时,它总是与"diff"。

如果 "same" 永远不会执行,那么 Python 如何确定执行或不执行的内容?如果他们确实执行了,他们的代码对象如何相同?


相同:

差异:


比较码:

def compare(fn1, fn2):
    for name in dir(fn1.__code__):
        if (name.startswith("co_") and
            name not in ("co_filename", "co_name", "co_firstlineno")):
            v1 = getattr(fn1.__code__, name)
            v2 = getattr(fn2.__code__, name)
            if v1 == v2:
                print(name.ljust(18), "same")
            else:
                print(name.ljust(18), "diff", v1, v2)

def f():
    pass

def g():
    10 ** 99; pass

以下不同:co_name(始终)、co_filename(IPython)、co_firstlineno(来自文件)- 但不影响 "executed", 如有错误请指正;与 docs 不同的是 co_code


注意:已接受的答案遗漏了一个重要的直觉:如果存储值所需的代码需要,则未分配的文字代码可能会保留比 存储表达式以计算值 所需的代码更多的内存; 10 ** 99 就是这种情况(至少,这就是评论中所断言的)。有关更多信息,请参阅答案下方的评论。

"diff"组的所有文字要么不是常量([]{}),要么不利于优化(例如10 ** 99小于其值) . "same" 组的所有表达式都计算为可以丢弃的常量。检查字节码显示表达式已完全删除:

>>> # CPython 3.7.4
>>> def g(): 10/1; pass
>>> dis.dis(g)
1           0 LOAD_CONST               0 (None)
            2 RETURN_VALUE

值得注意的是,none 已删除的表达式改变了可观察到的行为。 Python 实现是否删除不可观察的行为纯粹是一个实现细节。不会删除具有副作用的表达式,例如 1/0

>>> # CPython 3.7.4
>>> def g(): 10/0; pass
>>> dis.dis(g)
1           0 LOAD_CONST               1 (10)
            2 LOAD_CONST               2 (0)
            4 BINARY_TRUE_DIVIDE
            6 POP_TOP
            8 LOAD_CONST               0 (None)
           10 RETURN_VALUE

对于显示的表达式,字节码在 CPython 3.7.4、CPython 3.8.2、PyPy 3.6.9 [PyPy 7.3.0] 上是相同的。

在 CPython 3.4.3、CPython 2.7.10、PyPy 2.7.13 [PyPy 7.1.1] 上,常量表达式 10/1 被计算但未被丢弃。

>>> # CPython 3.4.3
>>> def g(): 10/1; pass
>>> dis.dis(g)
1           0 LOAD_CONST               3 (10.0)                                                                                  
            3 POP_TOP                                                                                                            
            4 LOAD_CONST               0 (None)                                                                                  
            7 RETURN_VALUE

表达式 "" 在我可用的任何 Python 实现中被丢弃。


由于这些优化是实现细节,因此没有正式的规范。如果需要更深入的了解,则应参考实现本身。对于 CPython,一个好的起点是 the peephole optimiser source code.

To keep the optimizer simple, it bails when the lineno table has complex encoding for gaps >= 255.

Optimizations are restricted to simple transformations occurring within a single basic block. All transformations keep the code size the same or smaller. For those that reduce size, the gaps are initially filled with NOPs. Later those NOPs are removed and the jump addresses retargeted in a single pass.