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。这与 exec
和 eval
等函数在其他情况下的行为相同,例如
执行代码包含私有变量。
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 字代码结构。)
我们已经知道函数参数曾经有 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。这与 exec
和 eval
等函数在其他情况下的行为相同,例如
执行代码包含私有变量。
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。 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 字代码结构。)