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

一个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(): ...:

我们已经知道函数参数过去有。但是,这种行为现在已经改变了,因为Python-3.7没有任何限制,除了
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