¿Por qué los cambios de Python 3 para que exec rompa este código?

Miré a través de los innumerables hilos de ‘Python exec‘ en SO, pero no pude encontrar uno que respondiera a mi problema. Lo siento mucho si esto se ha preguntado antes. Aquí está mi problema:

# Python 2.6: prints 'it is working' # Python 3.1.2: "NameError: global name 'a_func' is not defined" class Testing(object): def __init__(self): exec("""def a_func(): print('it is working')""") a_func() Testing() 

 # Python 2.6: prints 'it is working' # Python 3.1.2: prints 'it is working' class Testing(object): def __init__(self): def a_func(): print('it is working') a_func() Testing() 

Como la definición de la función estándar funciona en ambas versiones de Python, asumo que el problema debe ser un cambio en la forma en que funciona exec. Leí los documentos API para 2.6 y 3 para exec y también leí la página “What’s New In Python 3.0” y no pude ver ninguna razón por la que el código se rompiera.

Puede ver el código de bytes generado para cada versión de Python con:

 >>> from dis import dis 

Y, para cada intérprete:

 #Python 3.2 >>> dis(Testing.__init__) ... 5 10 LOAD_GLOBAL 1 (a_func) ... #Python 2.7 >>> dis(Testing.__init__) ... 5 8 LOAD_NAME 0 (a_func) ... 

Como puede ver, Python 3.2 busca un valor global (LOAD_GLOBAL) denominado a_func y 2.7 primero busca el ámbito local (LOAD_NAME) antes de buscar el global.

Si print(locals()) después del exec , verá que a_func se crea dentro de la función __init__ .

Realmente no sé por qué se hace de esa manera, pero parece ser un cambio en cómo se procesan las tablas de símbolos .

Por cierto, si desea crear un a_func = None encima de su método __init__ para que el intérprete sepa que es una variable local, no funcionará ya que el LOAD_FAST byte ahora será LOAD_FAST y eso no hace una búsqueda pero recibe directamente Valor de una lista.

La única solución que veo es agregar globals() como segundo argumento a exec , de modo que se cree a_func como una función global y se puede acceder mediante el LOAD_GLOBAL operación LOAD_GLOBAL .

Editar

Si elimina la statement exec , Python2.7 cambia el bytecode de LOAD_NAME a LOAD_GLOBAL . Entonces, usando exec , su código siempre será más lento en Python2.x porque tiene que buscar cambios en el ámbito local.

Como el exec de Python3 no es una palabra clave, el intérprete no puede estar seguro de si realmente está ejecutando un nuevo código o haciendo otra cosa … Por lo tanto, el código de bytes no cambia.

P.ej

 >>> exec = len >>> exec([1,2,3]) 3 

tl; dr

exec('...', globals()) puede resolver el problema si no le importa que el resultado se agregue al espacio de nombres global

Completando la respuesta anterior, por si acaso. Si el exec está en alguna función, recomendaría usar la versión de tres argumentos de la siguiente manera:

 def f(): d = {} exec("def myfunc(): ...", globals(), d) d["myfunc"]() 

Esta es la solución más limpia, ya que no modifica ningún espacio de nombres bajo tus pies. En su lugar, myfunc se almacena en el diccionario explícito d .