Ove*_*gon 5 python cpython python-3.x
我检查了.__code__对象的两个我认为不同但发现相同的函数,用于各种表达式。如果代码对象相同,据我所知,它们编译为相同的字节码,因此是“相同”的函数。
下表是之前插入的东西,; pass这使得g有不同的__code__。由于f是一个“什么都不做”的函数,这表明“相同”下的所有内容都不要执行,包括长算术。此外,元组是“相同的”,但列表和字符串是“差异”的——所以我们可能会得出结论,涉及不可变文字的未分配表达式不会被评估。但是,由于引发异常,这可能是“异常” - 那么vs.呢? 不会引发异常并且可以分配。1/010**9910**910**99
我无法从分析中看出太多;“相同”和“差异”都有无法区分的执行时间。然而,当它们可以被区分时,它总是带有“diff”。
如果“相同”从不执行,那么 Python 如何确定执行或不执行什么?如果它们确实执行,它们的代码对象如何相同?
相同:
0, (0,), True, False,None10 ** 9()-314159.265358 ** (1/12345) / 2.718281828 + 500 - 7j差异:
[0], {0: 0}10 ** 99[], {},""比较代码:
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
Run Code Online (Sandbox Code Playgroud)
以下不同:(co_name总是),co_filename(IPython),co_firstlineno(来自文件)-但不影响“执行”的内容,如果有误请纠正我;来自docs,co_code应该有所不同。
注意:接受的答案忽略了一个重要的直觉:如果存储值所需的代码比存储表达式所需的代码占用更多的内存来计算值,则可能会保留未分配的文字代码;情况就是这样10 ** 99(至少,这是评论中所断言的)。有关更多信息,请参阅答案下方的评论。
“diff”组的所有文字要么不是常量([], {}),要么不利于优化(例如10 ** 99小于其值)。“同一”组的所有表达式的计算结果都是可以被丢弃的常量。检查字节码显示表达式已完全删除:
>>> # CPython 3.7.4
>>> def g(): 10/1; pass
>>> dis.dis(g)
1 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
值得注意的是,删除的表达式都不会改变可观察的行为。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
Run Code Online (Sandbox Code Playgroud)
对于所示表达式,字节码在 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
Run Code Online (Sandbox Code Playgroud)
该表达式""在我可用的任何 Python 实现中都被丢弃。
由于这些优化是实现细节,因此没有正式的规范。如果需要更深入的理解,应该咨询实现本身。对于 CPython,窥视孔优化器源代码是一个很好的起点。
为了保持优化器简单,当 lineno 表具有间隙 >= 255 的复杂编码时,它会放弃。
优化仅限于单个基本块内发生的简单转换。所有转换都保持代码大小相同或更小。对于那些缩小尺寸的,间隙最初由 NOP 填充。随后,这些 NOP 被删除,并且跳转地址会在一次传递中重新定位。