namespace python python-3.x function namespaces python-internals

namespace - ¿Cuántas variables locales puede tener una función de Python(implementación de CPython)?



python namespace (2)

Ya sabemos que los argumentos de función solían tener el límite de 255 argumentos pasados ​​explícitamente . Sin embargo, este comportamiento se cambia ahora y, dado que Python-3.7 no tiene límite, excepto sys.maxsize que en realidad es el límite de los contenedores de python. Pero ¿qué pasa con las variables locales?

Básicamente, no podemos agregar variables locales a una función de manera dinámica y / o cambiar el diccionario locals() no está permitido directamente, por lo que incluso se puede probar esto de manera bruta. Pero el problema es que incluso si cambia los locals() usando el módulo compile o la función exec , no afecta la function.__code__.co_varnames , por lo tanto, no puede acceder a las variables explícitamente dentro de la función.

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'',)

Esto significa que incluso si usas un bucle for como:

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

Los locals() contendrán 2 ** 17 variables, pero no puede hacer algo como print(var_100) dentro de la función.

Sabemos que, básicamente, no es necesario agregar dinámicamente una variable a la función, mientras que puede usar un diccionario o, en otras palabras, un espacio de nombres personalizado. Pero, ¿cuál es la forma correcta de probar el límite del número máximo de variables locales en una función?


2 ^ 32. La LOAD_FAST utilizada para cargar variables locales solo tiene un oparg de 1 byte o de 2 bytes dependiendo de la versión de Python, pero puede extenderse hasta 4 bytes por una o más operaciones EXTENDED_ARG , permitiendo el acceso a 2 ^ 32 local. variables Puede ver algunos de los ayudantes utilizados para EXTENDED_ARG en Python/wordcode_helpers.h . (Tenga en cuenta que la documentación del código de operación para EXTENDED_ARG en los documentos no se ha actualizado todavía para reflejar la nueva estructura del código de palabra de Python 3.6).


Sobre exec() y su comportamiento con los locales, ya hay un debate abierto aquí: ¿Cómo funciona exec con los locales? .

Con respecto a la pregunta, parece prácticamente imposible probar eso agregando variables dinámicamente al espacio de nombres local que se comparte con la función __code__.co_varnames . Y la razón es que esto está restringido al código que se compila por bytes juntos . Este es el mismo comportamiento con el que funciones como exec y eval se limitan a otras situaciones, como la ejecución de códigos que contienen variables privadas.

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

Lea https://.com/a/49208472/2867928 para obtener más detalles.

Sin embargo, esto no significa que no podamos encontrar el límite en teoría. Al analizar la forma en que Python almacena las variables locales en la memoria.

La forma en que podemos hacer esto es mirar primero los códigos de bytes de una función y ver cómo se guardan las respectivas instrucciones en la memoria. El dis es una gran herramienta para desensamblar un código Python, que en caso de que podamos desmontar una función simple de la siguiente manera:

>>> # 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

Aquí el número más a la izquierda es el número de línea en la que se almacena el código. La columna de números después de ella son las compensaciones de cada instrucción en el bytecode.

El STOR_FAST operación STOR_FAST almacena TOS (parte superior de la pila) en los co_varnames[var_num] locales co_varnames[var_num] . Y como la diferencia de su desplazamiento con su siguiente código de operación es 3 (6 - 3), significa que cada STOR_FAST operación STOR_FAST solo ocupa 3 bytes de la memoria. El primer byte es almacenar la operación o el código de byte; los segundos dos bytes son el operando de ese código de byte, lo que significa que hay 2 ^ 16 combinaciones posibles.

Por lo tanto, en un byte_compile, teóricamente una función solo puede tener 65536 variables locales.

Después de Python-3.6, el intérprete de Python ahora usa un código de palabra de 16 bits en lugar de un código de bytes. Lo que en realidad es alinear las instrucciones para que siempre sean de 2 bytes en lugar de 1 o 3 teniendo argumentos que solo ocupan 1 byte.

Entonces, si realiza el desmontaje en versiones posteriores, obtendrá el siguiente resultado, que aún utiliza dos bytes para STORE_FAST .:

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

Sin embargo, @Alex Hall demostró en el comentario que puede exec una función completa con más de 2 ^ 16 variables que también están disponibles en __code__.co_varnames . Pero aún así, esto no significa que sea prácticamente factible probar la hipótesis (porque si intentas probar con potencias de más de 20, será exponencialmente cada vez más lento). Sin embargo, aquí está el código:

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

Esto significa que aunque STORE_FAST usa 2 bytes para preservar los TOS y "teóricamente" no puede conservar más de 2 ^ 16 variables diferentes, debería haber algún otro identificador único, como el número de desplazamiento, o espacio adicional que permita preservar más de 2 ^ 16 . Y resultó que es EXTENDED_ARG que, como se menciona en la documentación, prefija cualquier código de operación que tenga un argumento demasiado grande para que quepa en los dos bytes predeterminados. Por lo tanto, es 2 ^ 16 + 16 = 2 ^ 32 .

EXTENDED_ARG (ext) ¶

Prefija cualquier código de operación que tenga un argumento demasiado grande para ajustarse a los dos bytes predeterminados. ext contiene dos bytes adicionales que, tomados junto con el argumento del código de operación posterior, comprenden un argumento de cuatro bytes, siendo ext los dos bytes más significativos.