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

Jet*_*lue 1 python performance compiler-optimization

Python编译器是否可以识别在函数中定义的常量,这样,无论随后在代码中调用该函数多少次,它仅计算一次它们的值?

例如,

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

for i in range( 100 ):
    f()
Run Code Online (Sandbox Code Playgroud)

x重新计算100次f()调用吗?

并非总是可以在使用常量的函数之外定义常量,我很好奇Python是否在这种情况下得到了支持。

Wil*_*sem 6

简短的回答:对于列表来说,则不然

如果我们用 编译后检查中间代码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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,程序首先将常量加载14堆栈上并将它们压入堆栈,然后用这些常量构造一个列表,这意味着它每次都会构造一个列表。

如果列表没有发生变化,我建议在函数外部定义常量

some_constant = [1, 2, 3, 4]
def f():
    # use some_constant
    # ...
    pass
Run Code Online (Sandbox Code Playgroud)


Art*_*yer 5

(请注意,这适用于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
Run Code Online (Sandbox Code Playgroud)

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

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

如果您有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
Run Code Online (Sandbox Code Playgroud)

但这似乎是过早优化的情况。

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

>>> g.__code__.co_consts
(None, 1, 2, 3, 4, (1, 2, 3, 4))
Run Code Online (Sandbox Code Playgroud)

每次都必须构建一个新列表的原因是,以便在更改列表时,它不会影响每次加载的列表。

如果不是常量列表,则元组优化就会消失。

>>> 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
Run Code Online (Sandbox Code Playgroud)