¿Es posible escribir en un objeto de marco de python tal como lo devuelve sys._getframe () desde el código de python que se ejecuta dentro del intérprete?

A propósito de esta pregunta , hay un poco de andamiaje dentro del intérprete para inspeccionar objetos de marco, que puede ser recuperado por sys._getframe() . Los objetos del marco parecen ser de solo lectura, pero no puedo encontrar nada obvio en los documentos que explícitamente lo indiquen. ¿Alguien puede confirmar si estos objetos son de escritura (de alguna manera) o solo de lectura?

 import sys def foobar(): xx='foo' ff = sys._getframe() ff.f_locals['xx'] = 'bar' print xx if __name__ == '__main__': foobar() 

Esto imprime ‘ foo ‘ cuando se ejecuta, pero la publicación a continuación demuestra que la variable se puede escribir cuando se ejecuta desde el marco actual en un shell interactivo.

Desde la fuente de CPython, Objects/frameobject.c :

 static PyMemberDef frame_memberlist[] = { {"f_back", T_OBJECT, OFF(f_back), RO}, {"f_code", T_OBJECT, OFF(f_code), RO}, {"f_builtins", T_OBJECT, OFF(f_builtins),RO}, {"f_globals", T_OBJECT, OFF(f_globals), RO}, {"f_lasti", T_INT, OFF(f_lasti), RO}, {"f_exc_type", T_OBJECT, OFF(f_exc_type)}, {"f_exc_value", T_OBJECT, OFF(f_exc_value)}, {"f_exc_traceback", T_OBJECT, OFF(f_exc_traceback)}, {NULL} /* Sentinel */ }; ... static PyGetSetDef frame_getsetlist[] = { {"f_locals", (getter)frame_getlocals, NULL, NULL}, {"f_lineno", (getter)frame_getlineno, (setter)frame_setlineno, NULL}, {"f_trace", (getter)frame_gettrace, (setter)frame_settrace, NULL}, {"f_restricted",(getter)frame_getrestricted,NULL, NULL}, {0} }; 

Para PyMemberDef , los indicadores RO o READONLY significan que sus atributos son de solo lectura. Para PyGetSetDef , si solo tiene un captador, es de solo lectura. Esto significa que todos los atributos excepto f_exc_type , f_exc_value , f_exc_traceback y f_trace son de solo lectura después de la creación. Esto también se menciona en los documentos, bajo el modelo de datos .

Los objetos a los que hacen referencia los atributos no son necesariamente de solo lectura. Podrías hacer esto:

 >>> f = sys._getframe() >>> f.f_locals['foo'] = 3 >>> foo 3 >>> 

Aunque esto funciona en el intérprete, falla dentro de las funciones. El motor de ejecución utiliza una matriz separada para las variables locales ( f_fastlocals ), que se fusiona en f_locals en el acceso, pero lo contrario no es cierto.

 >>> def foo(): ... x = 3 ... f = sys._getframe() ... print f.f_locals['x'] ... x = 4 ... print f.f_locals['x'] ... d = f.f_locals ... x = 5 ... print d['x'] ... f.f_locals ... print d['x'] ... >>> foo() 3 4 4 5 >>> 

En el marco global, f_local refiere a f_globals , lo que hace que este truco funcione en el intérprete. La modificación de f_globals funciona, pero afecta a todo el módulo.

El ejemplo f_locals [‘foo’] de NXC funciona porque el código está en el scope del módulo. En ese caso, f_locals es f_globals, y f_globals es modificable y las modificaciones se reflejan en el módulo.

Dentro del scope de la función, locals () y f_locals se pueden escribir, pero “[los cambios pueden no afectar los valores de las variables locales utilizadas por el intérprete]”. 1 Es una opción de implementación. En CPython hay un bytecode optimizado para variables locales, LOAD_FAST. En Python, las variables locales se conocen (casi siempre) una vez que se define la función, y CPython utiliza una búsqueda de índice para obtener el valor de la variable, en lugar de una búsqueda de diccionario.

En teoría, la búsqueda en el diccionario podría representar esa tabla, pero eso es mucho trabajo por poco de ganancia.

Las excepciones a “las variables locales son conocidas” son si la función usa una statement exec, y el caso en desuso de “from module import *”. El código de bytes generado es diferente y más lento para estos casos.