一个Python(CPython实现)函数可能包含多少局部变量?
我们已经知道函数参数过去有。但是,这种行为现在已经改变了,因为Python-3.7没有任何限制,除了一个Python(CPython实现)函数可能包含多少局部变量?,python,python-3.x,function,namespaces,python-internals,Python,Python 3.x,Function,Namespaces,Python Internals,我们已经知道函数参数过去有。但是,这种行为现在已经改变了,因为Python-3.7没有任何限制,除了sys.maxsize,这实际上是Python容器的限制。但是局部变量呢 我们基本上不能以动态方式向函数中添加局部变量,并且/或者不允许直接更改locals()字典,这样我们甚至可以用蛮力的方式来测试这一点。但问题是,即使您使用compile模块或exec函数更改locals(),也不会影响函数。因此,您无法在函数内显式访问变量 In [142]: def bar(): ...:
sys.maxsize
,这实际上是Python容器的限制。但是局部变量呢
我们基本上不能以动态方式向函数中添加局部变量,并且/或者不允许直接更改locals()
字典,这样我们甚至可以用蛮力的方式来测试这一点。但问题是,即使您使用compile
模块或exec
函数更改locals()
,也不会影响函数。因此,您无法在函数内显式访问变量
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',)
locals()
将包含2**17个变量,但不能在函数内部执行类似于print(var_100)
的操作
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',)
我们知道,基本上不需要向函数动态添加变量,而可以使用字典或自定义名称空间。但是,测试函数中局部变量的最大数量限制的正确方法是什么?关于exec()
及其与局部变量的行为,这里已经有一个公开的争论:
关于这个问题,通过向与函数的\uuu code\uu.co\u varnames
共享的本地名称空间动态添加变量来测试这一点几乎是不可能的。原因是。这与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
这里最左边的数字是存储代码的行号。后面的数字列是字节码中每条指令的偏移量
操作码将TOS(堆栈顶部)存储到本地co\u varnames[var\u num]
中。由于它与下一个操作码的偏移量之差为3(6-3),这意味着每个STOR\u FAST
操作码只占用3个字节的内存。第一个字节用于存储操作或字节码;第二个两个字节是该字节码的操作数,这意味着存在可能的组合
因此,在单字节编译中,理论上一个函数只能有65536局部变量
在Python-3.6之后,实际上
因此,如果您在以后的版本中进行反汇编,您将得到以下结果,它仍然使用两个字节来存储\u 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个变量的完整函数,这些变量也可以在\uu code\uu.co\u 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\u FAST
使用2个字节来保存TOS,“理论上”不能保存超过2^16个不同的变量,但应该有一些其他唯一标识符,如偏移量,或额外的空间,使保存超过2^16个变量成为可能。正如文档中提到的,它在任何操作码前面加上前缀,因为它的参数太大,无法放入默认的两个字节。因此它是2^16+16=2^32
扩展参数(扩展)
前缀任何操作码,其参数太大,无法放入默认的两个字节。ext保存两个额外的字节,这些字节
与随后的操作码参数一起,组成一个四字节的
参数,ext是两个最重要的字节
2^32。用于加载局部变量的op只有一个1字节或2字节的oparg,具体取决于Python版本,但一个或多个op可以并且将扩展到4字节,允许访问2^32个局部变量。您可以在中看到用于EXTENDED_ARG
的一些帮助程序。(请注意,dis
文档中的EXTENDED_ARG
操作码文档尚未更新以反映新的Python 3.6 wordcode结构。)请注意,这个问题指的是CPython(参考实现),而不是Python(规范)。@Acorn确实刚刚更新。也许我误解了,但是我可以用2^17个局部变量构造和执行一个函数:@AlexHall这些变量应该存在于\uuucode\uuuu.co\u varnames
中。请查看有问题的更新以获得澄清。我认为你没有看过我的演示,或者至少没有理解它。我根据你写的内容更新了它。我不是执行个人任务,这是一个很大的功能。变量通常在各个方面都存在。@AlexHall是的,这很好。虽然我测试了一个类似的代码,但并没有达到预期的效果,但这个代码似乎工作得很好。但从理论上讲,这毫无意义@AlexHall我用你建议的代码更新了答案。谢谢你的评论。然而,python如何将这些TOS区分在一起还不是100%清楚。我在源代码中也找不到任何东西。没错。我正在查看wordcode\u helpers.h
文件。
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