python是否重复使用计算结果?
Does python reuse repeated calculation results?
如果我有一个表达式希望在 Python 中求值,例如下面代码片段中 r
的表达式,Python 解释器是否会智能并重用子结果 x+y+z
,还是只计算两次?我也很想知道这个问题的答案对于编译语言是否相同,例如C.
x = 1
y = 2
z = 3
r = (x+y+z+1) + (x+y+z+2)
有人提出这个问题类似于this question。我相信这是相似的。但是,我相信链接的问题不是 'minimal example'。同样在链接的问题中,操作顺序没有歧义,例如,在类似于此问题的示例中,没有定义操作顺序(数学上),具体取决于各个函数调用的顺序(它们是模棱两可),可能会完成更差或更好的优化工作。考虑 (abba)(abba)(baa*b),有嵌套的重复子串,根据预处理的顺序和数量,可以执行许多不同的优化。
不,python 默认情况下不这样做。如果您需要 python 为您保留某个计算的结果,您需要隐式告诉 python 这样做,一种方法是定义一个函数并使用 functools.lru_cache
docs:
from functools import lru_cache
@lru_cache(maxsize=32)
def add3(x,y,z):
return x + y + z
x=1
y=2
z=3
r = (add3(x,y,z)+1) + (add3(x,y,z)+2)
编辑: 此答案仅适用于 Python 语言的默认 CPython 解释器。它可能不适用于采用 JIT compilation techniques or uses a restricted Python sublanguage that allows static type inference. See 的其他 Python 实现以获取更多详细信息。
不,不会。您可以这样做以查看编译后的代码:
from dis import dis
dis("r=(x+y+z+1) + (x+y+z+2)")
输出:
0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 BINARY_ADD
6 LOAD_NAME 2 (z)
8 BINARY_ADD
10 LOAD_CONST 0 (1)
12 BINARY_ADD
14 LOAD_NAME 0 (x)
16 LOAD_NAME 1 (y)
18 BINARY_ADD
20 LOAD_NAME 2 (z)
22 BINARY_ADD
24 LOAD_CONST 1 (2)
26 BINARY_ADD
28 BINARY_ADD
30 STORE_NAME 3 (r)
32 LOAD_CONST 2 (None)
34 RETURN_VALUE
这部分是因为 Python 是动态类型的。所以编译时不容易知道变量的类型。并且编译器无法知道可以由用户 类 重载的 +
运算符是否有任何副作用。考虑以下简单示例:
class A:
def __init__(self, v):
self.value = v
def __add__(self, b):
print(b)
return self.value + b
x = A(3)
y = 4
r = (x + y + 1) + (x + y + 2)
对于简单的表达式,您可以将中间结果保存到一个新变量中:
z = x + y + 1
r = z + (z + 1)
对于函数调用,functools.lru_cache
是另一种选择,正如其他答案所指出的那样。
您可以使用 dis.dis
检查。输出是:
2 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (x)
3 4 LOAD_CONST 1 (2)
6 STORE_NAME 1 (y)
4 8 LOAD_CONST 2 (3)
10 STORE_NAME 2 (z)
5 12 LOAD_NAME 0 (x)
14 LOAD_NAME 1 (y)
16 BINARY_ADD
18 LOAD_NAME 2 (z)
20 BINARY_ADD
22 LOAD_CONST 0 (1)
24 BINARY_ADD
26 LOAD_NAME 0 (x)
28 LOAD_NAME 1 (y)
30 BINARY_ADD
32 LOAD_NAME 2 (z)
34 BINARY_ADD
36 LOAD_CONST 1 (2)
38 BINARY_ADD
40 BINARY_ADD
42 STORE_NAME 3 (r)
44 LOAD_CONST 3 (None)
46 RETURN_VALUE
因此它不会缓存括号中表达式的结果。虽然对于那个特定的情况是可能的,但一般来说它不是,因为自定义 类 可以定义 __add__
(或任何其他二进制操作)来修改自己。例如:
class Foo:
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += 1
return self.value + other
x = Foo(1)
y = 2
z = 3
print(x + y + z + 1) # prints 8
print(x + y + z + 1) # prints 9
如果你有一个昂贵的函数,你想缓存它的结果,你可以通过 functools.lru_cache
来实现。
另一方面,编译器会执行constant folding,从下面的例子可以看出:
>>> import dis
>>> dis.dis("x = 'abc' * 5")
1 0 LOAD_CONST 0 ('abcabcabcabcabc')
2 STORE_NAME 0 (x)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
>>> dis.dis("x = 1 + 2 + 3 + 4")
1 0 LOAD_CONST 0 (10)
2 STORE_NAME 0 (x)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
If I have an expression that I wish to evaluate in Python, such as the expression for r
in the code snippet below, will the Python interpreter be smart and reuse the subresult x+y+z
, or just evaluate it twice?
您指的是哪个 Python 口译员?目前有四种广泛使用的生产就绪、稳定的 Python 实现。 None 其中实际上有一个 Python 解释器,它们中的每一个都可以编译 Python.
在至少某些情况下,他们中的一些人可能会或可能不会对至少某些程序执行此优化。
Python 语言规范既不要求也不禁止这种优化,因此任何符合规范的 Python 实现都可以执行此优化,但不是必需的。
我非常确定,与所有其他说明 Python 无法做到这一点的答案相反,PyPy 能够执行此优化。此外,根据您使用的底层平台,使用 Jython 或 IronPython 执行的代码也将受益于此优化,例如我 100% 确定 Oracle HotSpot 的 C2 编译器 会执行此优化。
I'd also be interested to know if the answer to this question would be the same for a compiled language […].
没有 "compiled language" 这样的东西。编译和解释是编译器或解释器(duh!)的特征,而不是语言。每种语言都可以由编译器实现,每种语言都可以由解释器实现。恰当的例子:有 C 的解释器,相反,每个当前存在的生产就绪、稳定、广泛使用的 Python、ECMAScript、Ruby 和 PHP 的实现至少有一个编译器,许多编译器甚至不止一个(例如 PyPy、V8、SpiderMonkey、Squirrelfish Extreme、Chakra)。
语言是写在一张纸上的一组抽象的数学规则和限制。一种语言既不编译也不解释,一种语言只是是。这些概念存在于不同的抽象层上;如果英语是一种打字语言,术语 "compiled language" 将是打字错误。
I'd also be interested to know if the answer to this question would be the same for […] e.g. C.
有许多广泛使用的生产就绪、稳定的 C 实现。在至少某些情况下,他们中的一些人可能会或可能不会对至少某些程序执行此优化。
C 语言规范既不要求也不禁止这种优化,因此任何符合规范的 C 实现都可以执行这种优化,但不是必需的。
如果我有一个表达式希望在 Python 中求值,例如下面代码片段中 r
的表达式,Python 解释器是否会智能并重用子结果 x+y+z
,还是只计算两次?我也很想知道这个问题的答案对于编译语言是否相同,例如C.
x = 1
y = 2
z = 3
r = (x+y+z+1) + (x+y+z+2)
有人提出这个问题类似于this question。我相信这是相似的。但是,我相信链接的问题不是 'minimal example'。同样在链接的问题中,操作顺序没有歧义,例如,在类似于此问题的示例中,没有定义操作顺序(数学上),具体取决于各个函数调用的顺序(它们是模棱两可),可能会完成更差或更好的优化工作。考虑 (abba)(abba)(baa*b),有嵌套的重复子串,根据预处理的顺序和数量,可以执行许多不同的优化。
不,python 默认情况下不这样做。如果您需要 python 为您保留某个计算的结果,您需要隐式告诉 python 这样做,一种方法是定义一个函数并使用 functools.lru_cache
docs:
from functools import lru_cache
@lru_cache(maxsize=32)
def add3(x,y,z):
return x + y + z
x=1
y=2
z=3
r = (add3(x,y,z)+1) + (add3(x,y,z)+2)
编辑: 此答案仅适用于 Python 语言的默认 CPython 解释器。它可能不适用于采用 JIT compilation techniques or uses a restricted Python sublanguage that allows static type inference. See
不,不会。您可以这样做以查看编译后的代码:
from dis import dis
dis("r=(x+y+z+1) + (x+y+z+2)")
输出:
0 LOAD_NAME 0 (x)
2 LOAD_NAME 1 (y)
4 BINARY_ADD
6 LOAD_NAME 2 (z)
8 BINARY_ADD
10 LOAD_CONST 0 (1)
12 BINARY_ADD
14 LOAD_NAME 0 (x)
16 LOAD_NAME 1 (y)
18 BINARY_ADD
20 LOAD_NAME 2 (z)
22 BINARY_ADD
24 LOAD_CONST 1 (2)
26 BINARY_ADD
28 BINARY_ADD
30 STORE_NAME 3 (r)
32 LOAD_CONST 2 (None)
34 RETURN_VALUE
这部分是因为 Python 是动态类型的。所以编译时不容易知道变量的类型。并且编译器无法知道可以由用户 类 重载的 +
运算符是否有任何副作用。考虑以下简单示例:
class A:
def __init__(self, v):
self.value = v
def __add__(self, b):
print(b)
return self.value + b
x = A(3)
y = 4
r = (x + y + 1) + (x + y + 2)
对于简单的表达式,您可以将中间结果保存到一个新变量中:
z = x + y + 1
r = z + (z + 1)
对于函数调用,functools.lru_cache
是另一种选择,正如其他答案所指出的那样。
您可以使用 dis.dis
检查。输出是:
2 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (x)
3 4 LOAD_CONST 1 (2)
6 STORE_NAME 1 (y)
4 8 LOAD_CONST 2 (3)
10 STORE_NAME 2 (z)
5 12 LOAD_NAME 0 (x)
14 LOAD_NAME 1 (y)
16 BINARY_ADD
18 LOAD_NAME 2 (z)
20 BINARY_ADD
22 LOAD_CONST 0 (1)
24 BINARY_ADD
26 LOAD_NAME 0 (x)
28 LOAD_NAME 1 (y)
30 BINARY_ADD
32 LOAD_NAME 2 (z)
34 BINARY_ADD
36 LOAD_CONST 1 (2)
38 BINARY_ADD
40 BINARY_ADD
42 STORE_NAME 3 (r)
44 LOAD_CONST 3 (None)
46 RETURN_VALUE
因此它不会缓存括号中表达式的结果。虽然对于那个特定的情况是可能的,但一般来说它不是,因为自定义 类 可以定义 __add__
(或任何其他二进制操作)来修改自己。例如:
class Foo:
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += 1
return self.value + other
x = Foo(1)
y = 2
z = 3
print(x + y + z + 1) # prints 8
print(x + y + z + 1) # prints 9
如果你有一个昂贵的函数,你想缓存它的结果,你可以通过 functools.lru_cache
来实现。
另一方面,编译器会执行constant folding,从下面的例子可以看出:
>>> import dis
>>> dis.dis("x = 'abc' * 5")
1 0 LOAD_CONST 0 ('abcabcabcabcabc')
2 STORE_NAME 0 (x)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
>>> dis.dis("x = 1 + 2 + 3 + 4")
1 0 LOAD_CONST 0 (10)
2 STORE_NAME 0 (x)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
If I have an expression that I wish to evaluate in Python, such as the expression for
r
in the code snippet below, will the Python interpreter be smart and reuse the subresultx+y+z
, or just evaluate it twice?
您指的是哪个 Python 口译员?目前有四种广泛使用的生产就绪、稳定的 Python 实现。 None 其中实际上有一个 Python 解释器,它们中的每一个都可以编译 Python.
在至少某些情况下,他们中的一些人可能会或可能不会对至少某些程序执行此优化。
Python 语言规范既不要求也不禁止这种优化,因此任何符合规范的 Python 实现都可以执行此优化,但不是必需的。
我非常确定,与所有其他说明 Python 无法做到这一点的答案相反,PyPy 能够执行此优化。此外,根据您使用的底层平台,使用 Jython 或 IronPython 执行的代码也将受益于此优化,例如我 100% 确定 Oracle HotSpot 的 C2 编译器 会执行此优化。
I'd also be interested to know if the answer to this question would be the same for a compiled language […].
没有 "compiled language" 这样的东西。编译和解释是编译器或解释器(duh!)的特征,而不是语言。每种语言都可以由编译器实现,每种语言都可以由解释器实现。恰当的例子:有 C 的解释器,相反,每个当前存在的生产就绪、稳定、广泛使用的 Python、ECMAScript、Ruby 和 PHP 的实现至少有一个编译器,许多编译器甚至不止一个(例如 PyPy、V8、SpiderMonkey、Squirrelfish Extreme、Chakra)。
语言是写在一张纸上的一组抽象的数学规则和限制。一种语言既不编译也不解释,一种语言只是是。这些概念存在于不同的抽象层上;如果英语是一种打字语言,术语 "compiled language" 将是打字错误。
I'd also be interested to know if the answer to this question would be the same for […] e.g. C.
有许多广泛使用的生产就绪、稳定的 C 实现。在至少某些情况下,他们中的一些人可能会或可能不会对至少某些程序执行此优化。
C 语言规范既不要求也不禁止这种优化,因此任何符合规范的 C 实现都可以执行这种优化,但不是必需的。