为啥 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中为啥从str到bytes的转化会出现类似' \x** '的形式