Python编译器和函数内定义的常量

时间:2017-10-25 19:28:09

标签: python performance compiler-optimization

Python编译器是否识别函数中定义的常量,这样它只会计算一次它们的值,而不管函数随后在代码中调用多少次?

例如,

def f():
    x = [ 1, 2, 3, 4 ]
    # stuff

for i in range( 100 ):
    f()

x重新计算100次f()

并不总是可以在使用它们的函数之外定义常量,并且我很好奇Python是否已经在这些情况下退缩了。

2 个答案:

答案 0 :(得分:3)

(请注意,这适用于CPython,在其他实现中可能有所不同)

解析Python代码并将其编译为字节码。您可以看到dis模块使用的说明。

>>> def f(x):
...     x = [1, 2, 3, 4]
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               3 (3)
              6 LOAD_CONST               4 (4)
              8 BUILD_LIST               4
             10 STORE_FAST               0 (x)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

>>> print(dis.Bytecode(f).info())
Name:              f
Filename:          <stdin>
Argument count:    1
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
   1: 1
   2: 2
   3: 3
   4: 4
Variable names:
   0: x

如您所见,整数文字是常量,但每次都必须构建列表。

这是一个相对较快的操作(可能比查找全局更快,但时间仍然可以忽略不计)

如果你有一个使用元组的函数g,它将作为常量加载:

>>> def g(x):
...     x = (1, 2, 3, 4)
>>> dis.dis(g)
  2           0 LOAD_CONST               5 ((1, 2, 3, 4))
              2 STORE_FAST               0 (x)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
>>> print(dis.Bytecode(g).info())
Name:              g
Filename:          <stdin>
Argument count:    1
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
   1: 1
   2: 2
   3: 3
   4: 4
   5: (1, 2, 3, 4)
Variable names:
   0: x

但这似乎是一个过早优化的案例。

为函数存储的常量可以找到function.__code__.co_consts

>>> g.__code__.co_consts
(None, 1, 2, 3, 4, (1, 2, 3, 4))

每次必须构建新列表的原因是,如果更改列表,它将不会影响每次加载的列表。

如果它不是常量列表,那么元组优化会消失。

>>> def h(x):
...     x = (1, 2, 3, x)
>>> dis.dis(h)
  2           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               3 (3)
              6 LOAD_FAST                0 (x)
              8 BUILD_TUPLE              4
             10 STORE_FAST               0 (x)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE
>>> print(dis.Bytecode(h).info())
Name:              h
Filename:          <stdin>
Argument count:    1
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
   1: 1
   2: 2
   3: 3
Variable names:
   0: x

答案 1 :(得分:1)

简短回答:对于列表

如果我们在使用dis进行编译后检查中间代码,我们会看到:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 BUILD_LIST               4
             15 STORE_FAST               0 (x)
             18 LOAD_CONST               0 (None)
             21 RETURN_VALUE

因为你可以看到程序首先将常量1加载到4并将它们推送到堆栈上,并构造一个包含这些常量的列表,这意味着它构造了一个列表每次

如果列表没有变异,我建议定义函数外的常量

some_constant = [1, 2, 3, 4]
def f():
    # use some_constant
    # ...
    pass