为啥 Python 在编译为字节码之前不评估常数算术?

Posted

技术标签:

【中文标题】为啥 Python 在编译为字节码之前不评估常数算术?【英文标题】:Why doesn't Python evaluate constant number arithmetic before compiling to bytecode?为什么 Python 在编译为字节码之前不评估常数算术? 【发布时间】:2012-03-12 14:33:37 【问题描述】:

在下面的代码中,为什么Python没有将f2编译成与f1相同的字节码?

有理由不这样做吗?

>>> def f1(x):
    x*100

>>> dis.dis(f1)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (100)
              6 BINARY_MULTIPLY
              7 POP_TOP
              8 LOAD_CONST               0 (None)
             11 RETURN_VALUE
>>> def f2(x):
        x*10*10

>>> dis.dis(f2)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (10)
              6 BINARY_MULTIPLY
              7 LOAD_CONST               1 (10)
             10 BINARY_MULTIPLY
             11 POP_TOP
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE

【问题讨论】:

【参考方案1】:

这是因为x 可能有一个带有副作用的__mul__ 方法。 x * 10 * 10 调用__mul__ 两次,而x * 100 只调用一次:

>>> class Foo(object):
...     def __init__ (self):
...             self.val = 5
...     def __mul__ (self, other):
...             print "Called __mul__: %s" % (other)
...             self.val = self.val * other
...             return self
... 
>>> a = Foo()
>>> a * 10 * 10
Called __mul__: 10
Called __mul__: 10
<__main__.Foo object at 0x1017c4990>

自动折叠常量并且只调用一次__mul__ 可以改变行为。

您可以通过重新排序操作来获得所需的优化,以便首先将常量相乘(或者,如 cmets 中所述,使用括号将它们分组,以便它们仅一起操作,而不管位置如何),因此明确表示您希望折叠发生:

>>> def f1(x):
...     return 10 * 10 * x
... 
>>> dis.dis(f1)
  2           0 LOAD_CONST               2 (100)
              3 LOAD_FAST                0 (x)
              6 BINARY_MULTIPLY     
              7 RETURN_VALUE 

【讨论】:

请注意,即使是什么也没有传递给方法,dis 的结果仍然是相同的,即如果方法的内容是:x = 9; y = x * 10 * 10;,结果仍然是相同的即。加载 const 两次。看来Python没有做全方法优化? @SanjayT.Sharma:编译器仍然无法真正知道x 是什么,所以它必须谨慎行事。 Python 的多功能自省和动态运行时修改功能使得在函数 locals 中更改 x 的类型成为可能。 它可能有一个非关联的__mul__ 方法,或者不接受RHS 上的int,就此而言。 @KarlKnechtel:当然-我会将第一个归类为“有副作用”,第二个与您是否可以优化 RHS 常量乘法无关-如果语法允许,您仍然可以这样做在编译器中并在 __mul__ 方法中正确失败 我想开玩笑说 x * 10 * 10 == x * 101 对于 10 的大值。这个答案表明这个笑话有一些价值,因为它说明了,据我们所知,x .__mul__ 可以实现区间算术。 (我已经重写了这条评论,以消除“字面上”这个词,作为对 Blindy 的让步。)【参考方案2】:

Python 计算来自left to right 的表达式。对于f2(),这意味着它将首先评估x*10,然后将结果乘以10。试试:

试试:

def f2(x):
    10*10*x

这应该被优化。

【讨论】:

酷!也在 cpython 2.6.6 上确认

以上是关于为啥 Python 在编译为字节码之前不评估常数算术?的主要内容,如果未能解决你的问题,请参考以下文章

Python 文件编译为字节码的方法

python中为啥从str到bytes的转化会出现类似' \x** '的形式

python编译文件(pyc)即使编译为字节码也需要系统上的导入库吗?

java和python对比

为啥两个 Uni V3 池有不同的字节码?

C 定义的结构数组的元素在编译为 C++ 时具有不完整的类型