这两个函数为什么不同?

26

看一下这个:

>>> def f():
...     return (2+3)*4
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE  

显然,编译器已经预先计算了(2+3)*4,这是有意义的。

现在,如果我只是改变*的操作数顺序:

>>> def f():
...     return 4*(2+3)
... 
>>> dis(f)
  2           0 LOAD_CONST               1 (4)
              3 LOAD_CONST               4 (5)
              6 BINARY_MULTIPLY     
              7 RETURN_VALUE  

这个表达式不再完全预先计算了!原因是什么?我正在使用CPython 2.7.3。


2
在我看来,这似乎是望远镜优化器中的一个缺陷。你可以检查一下错误跟踪器,看看它是否是一个已知的问题。 - user2357112
4
停止使用旧版的 Python 2.x……在3.3版本(或更高版本)中,它可以正常工作。 - JBernardo
1
@JBernardo 2.x和3.x非常不同;我无法无缝切换它们。 - arshajii
2
它已经有5年了。人们应该开始过渡。 - JBernardo
3
@JBernardo说:自关键扩展模块被移植到Python 3以来,时间远不到5年。例如,SciPy仅在过去两年内正式支持Python 3。将大型现有代码库移植到新语言是昂贵且需要时间的。预计Python 2.7将在未来许多年保持活跃和可用。 - Mr Fooz
有趣的是,看起来这个问题在3.1和3.2中仍然存在。 - arshajii
2个回答

9
在第一个示例中,未经优化的代码是LOAD 2 LOAD 3 ADD LOAD 4 MULTIPLY,而在第二个示例中,它是LOAD 4 LOAD 2 LOAD 3 ADD MULTIPLYfold_binops_on_constants()中的模式匹配器必须正确处理第一个ADD(将LOAD LOAD ADD替换为LOAD),然后继续对MULTIPLY执行相同的操作。 在第二种情况下,当ADD(现在是MULTIPLY的第二个参数而不是第一个参数)变成常量时,扫描仪已经太远而无法看到L L M(当“光标”位于LOAD 4上时,它还没有看起来像L L M)。

那么这是编译器的一个缺陷,对吗? - arshajii
3
@arshajii:你可以通过在折叠操作后向后备份一条指令来修复peephole.c中的漏洞。但是正确的解决方案是不要依赖于Peephole 优化器,而是在compile.c中折叠常量,因为它是首次发出二进制操作的地方。对 AST 进行传递,可以将常量表达式信息从叶子节点上传播上来,这样就没有机会出现这种错误了。 - Ben Jackson

5

看起来这个问题在Python 3.3中已经修复,可以在这里看到。

>>> def f():
...     return (2+3)*4
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE  
>>> def f():
...     return 4*(2+3)
... 
>>> dis(f)
  2           0 LOAD_CONST               5 (20)
              3 RETURN_VALUE 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接