Python(CPython 实现)函数可能包含多少局部变量?

How many local variables can a Python (CPython implementation) function possibly hold?

我们已经知道函数参数曾经有 limit of 255 explicitly passed arguments。但是,此行为现在已更改,因为 Python-3.7 没有限制,除了 sys.maxsize 实际上是 python 容器的限制。但是局部变量呢?

我们基本上不能以动态方式将局部变量添加到函数中 and/or 不允许直接更改 locals() 字典,因此甚至可以用蛮力方式对其进行测试。但问题是,即使您使用 compile 模块或 exec 函数更改 locals(),它也不会影响 function.__code__.co_varnames,因此,您无法显式访问其中的变量功能。

In [142]: def bar():
     ...:     exec('k=10')
     ...:     print(f"locals: {locals()}")
     ...:     print(k)
     ...:     g = 100
     ...:     
     ...:     

In [143]: bar()
locals: {'k': 10}
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-143-226d01f48125> in <module>()
----> 1 bar()

<ipython-input-142-69d0ec0a7b24> in bar()
      2     exec('k=10')
      3     print(f"locals: {locals()}")
----> 4     print(k)
      5     g = 100
      6 

NameError: name 'k' is not defined

In [144]: bar.__code__.co_varnames
Out[144]: ('g',)

这意味着即使您使用 for 循环,如:

for i in range(2**17):
    exec(f'var_{i} = {i}')

locals() 将包含 2**17 个变量,但您不能在函数内部执行类似 print(var_100) 的操作。

我们知道基本上不需要动态添加变量到函数中,而可以使用字典或换句话说自定义命名空间。但是测试函数中局部变量的最大数量限制的正确方法是什么?

关于 exec() 及其与当地人的行为,这里已经有公开辩论:How does exec work with locals?

关于这个问题,通过将变量动态添加到与函数 __code__.co_varnames 共享的本地名称 space 来测试它似乎几乎不可能。原因是this is restricted to code that is byte-compiled together。这与 execeval 等函数在其他情况下的行为相同,例如 执行代码包含私有变量。

In [154]: class Foo:
     ...:     def __init__(self):
     ...:         __private_var = 100
     ...:         exec("print(__private_var)")

In [155]: f = Foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-155-79a961337674> in <module>()
----> 1 f = Foo()

<ipython-input-154-278c481fbd6e> in __init__(self)
      2     def __init__(self):
      3         __private_var = 100
----> 4         exec("print(__private_var)")
      5 
      6 

<string> in <module>()

NameError: name '__private_var' is not defined

阅读 了解更多详情。

但是,这并不意味着我们无法找出theory.i中的极限。e通过分析python在内存中存储局部变量的方式。

我们可以这样做的方法是首先查看一个函数的字节码,看看各个指令是如何存储在内存中的。 dis 是反汇编 Python 代码的好工具,如果我们可以反汇编一个简单的函数,如下所示:

>>> # VERSIONS BEFORE PYTHON-3.6
>>> import dis
>>> 
>>> def foo():
...     a = 10
... 
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (10)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

这里最左边的数字是代码所在的行数。它后面的一列数字是每条指令在字节码中的偏移量。

STOR_FAST 操作码将 TOS(栈顶)存储到本地 co_varnames[var_num]。并且由于其偏移量与其下一个操作码的差值为 3(6 - 3),这意味着每个 STOR_FAST 操作码仅占用 3 个字节的内存。第一个字节存放操作或字节码;后两个字节是该字节码的操作数,这意味着有 2^16 可能的组合。

因此,在一个byte_compile中,理论上一个函数只能有65536个局部变量。

Python-3.6the Python interpreter now uses a 16-bit wordcode instead of bytecode. Which is actually aligning the instructions to always be 2 bytes rather than 1 or 3 by having arguments only take up 1 byte.

之后

所以如果你在以后的版本中进行反汇编,你会得到以下结果,它仍然使用两个字节作为 STORE_FAST.:

>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (10)
              2 STORE_FAST               0 (a)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE

但是,@Alex Hall 在评论中表明,您可以 exec 一个完整的函数,其中包含超过 2^16使它们在 __code__.co_varnames 中也可用的变量。但这仍然并不意味着检验该假设实际上是可行的(因为如果您尝试使用超过 20 的幂进行检验,它会以指数方式变得越来越耗时)。但是,这里是代码:

In [23]: code = '''
    ...: def foo():
    ...: %s
    ...:     print('sum:', sum(locals().values()))
    ...:     print('add:', var_100 + var_200)
    ...: 
    ...: ''' % '\n'.join(f'    var_{i} = {i}'
    ...:                 for i in range(2**17))
    ...:                 
    ...:                 
    ...:                 

In [24]: foo()
sum: 549755289600
add: 300

In [25]: len(foo.__code__.co_varnames)
Out[25]: 1048576

这意味着虽然 STORE_FAST 使用 2 个字节来保存 TOS 而 "theoretically" 不能保存超过 2^16 个不同的变量,但是应该是一些其他的唯一标识符,比如偏移号,或者额外的 space 可以保留超过 2^16 it's EXTENDED_ARG 正如文档中提到的那样,它为参数太大而无法放入默认两个字节的任何操作码添加前缀。因此它是 2^16 + 16 = 2^32.

EXTENDED_ARG(ext)¶

Prefixes any opcode which has an argument too big to fit into the default two bytes. ext holds two additional bytes which, taken together with the subsequent opcode’s argument, comprise a four-byte argument, ext being the two most-significant bytes.

2^32。 LOAD_FAST op used for loading local variables only has a 1-byte or 2-byte oparg depending on the Python version, but this can and will be extended up to 4 bytes by one or more EXTENDED_ARG ops, allowing access to 2^32 local variables. You can see some of the helpers used for EXTENDED_ARG in Python/wordcode_helpers.h。 (请注意,dis 文档中 EXTENDED_ARG 的操作码文档尚未更新以反映新的 Python 3.6 字代码结构。)