¿Por qué es__code__ para una función(Python) mutable
internals python-internals (1)
El hecho es que la mayoría de las cosas en Python son mutables. Entonces la pregunta real es, ¿por qué no son __closure__
y __globals__
?
La respuesta inicialmente parece simple. Ambas cosas son contenedores de variables que la función podría necesitar. El objeto de código en sí no lleva consigo sus variables globales y cerradas; simplemente sabe cómo obtenerlos de la función. Toma los valores reales de estos dos atributos cuando se llama a la función.
Pero los propios ámbitos son mutables, por lo que esta respuesta es insatisfactoria. Necesitamos explicar por qué modificar estas cosas en particular rompería las cosas.
Para __closure__
, podemos mirar su estructura. No es un mapeo, sino una tupla de celdas. No conoce los nombres de las variables cerradas . Cuando el objeto de código busca una variable cerrada, necesita saber su posición en la tupla; se combinan uno a uno con co_freevars
que también es de solo lectura. Y si la tupla es del tamaño incorrecto o no es una tupla en absoluto, este mecanismo se rompe, probablemente de manera violenta (lea: segfaults) si el código C subyacente no espera tal situación. Forzar el código C para verificar el tipo y tamaño de la tupla es un trabajo innecesario que no se puede eliminar al hacer que el atributo sea de solo lectura. Si intentas reemplazar __code__
con algo que tome un número diferente de variables libres, obtendrás un error , por lo que el tamaño siempre es correcto.
Para __globals__
, la explicación es menos obvia, pero especularé. El mecanismo de búsqueda de alcance espera tener acceso al espacio de nombres global en todo momento. De hecho, el hard-coded bytes puede estar hard-coded para ir directamente al espacio de nombres global, si el compilador puede probar que ningún otro espacio de nombres tendrá una variable con un nombre en particular. Si el espacio de nombres global fuera de repente None
o algún otro objeto no cartográfico, el código C podría, una vez más, comportarse violentamente. Una vez más, hacer que el código realice verificaciones de tipo innecesarias sería una pérdida de ciclos de CPU.
Otra posibilidad es que las funciones (normalmente declaradas) tomen prestada una referencia al espacio de nombres global del módulo, y hacer que el atributo sea escribible causaría que el recuento de referencias se confunda. Podría imaginar este diseño, pero no estoy realmente seguro de que sea una gran idea, ya que las funciones se pueden construir explícitamente con objetos cuyas vidas pueden ser más cortas que las del módulo propietario, y estos deberían ser de carácter especial.
En una pregunta anterior de ayer, en comentarios, llegué a saber que en python __code__
atributo de una función es mutable. Por lo tanto puedo escribir código como sigue
def foo():
print "Hello"
def foo2():
print "Hello 2"
foo()
foo.__code__ = foo2.__code__
foo()
Salida
Hello
Hello 2
Intenté buscar en Google, pero ya sea porque no hay información (lo dudo mucho), o la palabra clave ( __code__
) no es fácil de buscar, no pude encontrar un caso de uso para esto.
No parece que "porque la mayoría de las cosas en Python son mutables" es una respuesta razonable, porque otros atributos de las funciones - __closure__
y __globals__
- son explícitamente de solo lectura (de Objects/funcobject.c ):
static PyMemberDef func_memberlist[] = {
{"__closure__", T_OBJECT, OFF(func_closure),
RESTRICTED|READONLY},
{"__doc__", T_OBJECT, OFF(func_doc), PY_WRITE_RESTRICTED},
{"__globals__", T_OBJECT, OFF(func_globals),
RESTRICTED|READONLY},
{"__module__", T_OBJECT, OFF(func_module), PY_WRITE_RESTRICTED},
{NULL} /* Sentinel */
};
¿Por qué se podría escribir __code__
mientras que otros atributos son de solo lectura?