Comportamiento de la función exec en Python 2 y Python 3

El siguiente código da una salida diferente en Python2 y en Python3 :

 from sys import version print(version) def execute(a, st): b = 42 exec("b = {}\nprint('b:', b)".format(st)) print(b) a = 1. execute(a, "1.E6*a") 

Python2 imprime:

 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] ('b:', 1000000.0) 1000000.0 

Impresiones de Python3 :

 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] b: 1000000.0 42 

¿Por qué Python2 vincula la variable b dentro de la función de execute a los valores en la cadena de la función exec , mientras que Python3 no hace esto? ¿Cómo puedo lograr el comportamiento de Python2 en Python3 ? Ya intenté pasar diccionarios para globales y locales a la función exec en Python3 , pero hasta ahora no funcionó.

— EDITAR —

Después de leer la respuesta de Martijns, analicé esto con Python3 . En el siguiente ejemplo, le doy a los locals() exec de exec como d a exec , pero d['b'] imprime algo más que solo b .

 from sys import version print(version) def execute(a, st): b = 42 d = locals() exec("b = {}\nprint('b:', b)".format(st), globals(), d) print(b) # This prints 42 print(d['b']) # This prints 1000000.0 print(id(d) == id(locals())) # This prints True a = 1. execute(a, "1.E6*a") 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] b: 1000000.0 42 1000000.0 True 

La comparación de los identificadores de d y locals() muestra que son el mismo objeto. Pero en estas condiciones, b debería ser lo mismo que d['b'] . ¿Qué está mal en mi ejemplo?

Hay una gran diferencia entre exec en Python 2 y exec() en Python 3. Usted está tratando a exec como una función, pero realmente es una statement en Python 2.

Debido a esta diferencia, no se pueden cambiar las variables locales en el scope de la función en Python 3 utilizando exec , incluso aunque fuera posible en Python 2. Ni siquiera las variables declaradas previamente.

locals() solo refleja las variables locales en una direccion. Lo siguiente nunca funcionó en 2 o 3:

 def foo(): a = 'spam' locals()['a'] = 'ham' print(a) # prints 'spam' 

En Python 2, el uso de la sentencia exec significaba que el comstackdor sabía que debía desactivar las optimizaciones de scope local (por ejemplo, cambiar de LOAD_FAST a LOAD_NAME para buscar variables tanto en el ámbito local como global). Dado que exec() es una función, esa opción ya no está disponible y los ámbitos de funciones ahora siempre están optimizados.

Además, en Python 2, la statement exec copia explícitamente todas las variables encontradas en locals() nuevo a la función locals usando PyFrame_LocalsToFast , pero solo si no se proporcionaron parámetros globales y locales .

La solución alternativa adecuada es utilizar un nuevo espacio de nombres (un diccionario) para su llamada exec() :

 def execute(a, st): namespace = {} exec("b = {}\nprint('b:', b)".format(st), namespace) print(namespace['b']) 

La documentación de exec() es muy explícita acerca de esta limitación:

Nota: los locales predeterminados actúan como se describe para la función locals() continuación: no deben intentarse modificaciones al diccionario local predeterminado. Pase un diccionario de locals explícito si necesita ver los efectos del código en los locales después de que devuelve la función exec() .

Yo diría que es un error de python3.

 def u(): exec("a=2") print(locals()['a']) u() 

impresiones “2”.

 def u(): exec("a=2") a=2 print(a) u() 

impresiones “2”.

Pero

 def u(): exec("a=2") print(locals()['a']) a=2 u() 

falla con

 Traceback (most recent call last): File "", line 1, in  File "", line 3, in u KeyError: 'a' 

— EDITAR — Otro comportamiento interesante:

 def u(): a=1 l=locals() exec("a=2") print(l) u() def u(): a=1 l=locals() exec("a=2") locals() print(l) u() 

salidas

 {'l': {...}, 'a': 2} {'l': {...}, 'a': 1} 

Y también

 def u(): l=locals() exec("a=2") print(l) print(locals()) u() def u(): l=locals() exec("a=2") print(l) print(locals()) a=1 u() 

salidas

 {'l': {...}, 'a': 2} {'l': {...}, 'a': 2} {'l': {...}, 'a': 2} {'l': {...}} 

Al parecer, la acción del exec sobre los locales es la siguiente:

  • Si una variable se establece dentro de exec y esta variable es una variable local, exec modifica el diccionario interno (el que devolvió locals() ) y no lo devuelve a su estado original. Una llamada a locals() actualiza el diccionario (como se documenta en la sección 2 de la documentación de Python), y se olvida el valor establecido en exec . La necesidad de llamar a locals() para actualizar el diccionario no es un error de python3, porque está documentado, pero no es intuitivo. Además, el hecho de que las modificaciones de los locales en exec no cambien los locales de la función es una diferencia documentada con python2 (la documentación dice “Pase un diccionario de locales explícito si necesita ver los efectos del código en los locales después de la función exec ( ) devuelve “), y prefiero el comportamiento de python2.
  • Si una variable se establece dentro de exec y esta variable no existía antes, exec modifica el diccionario interno a menos que la variable se establezca después. Parece que hay un error en la forma en que locals() actualiza el diccionario; este error da acceso al valor establecido dentro de exec llamando a locals() después de exec .

Para resumirlo:

  • No hay ningún error en Python 2 ni en Python 3
  • El comportamiento diferente de exec deriva de exec como una statement en Python 2, mientras que se convirtió en una función en Python 3.

Tenga en cuenta:

No digo nada nuevo aquí. Esto es solo una asamblea de la verdad que se encuentra en todas las demás respuestas y comentarios. Todo lo que bash aquí es aclarar algunos de los detalles más oscuros.

La única diferencia entre Python 2 y Python 3 es que, de hecho, exec puede cambiar el scope local de la función de cierre en Python 2 (porque es una statement y puede acceder al scope local actual) y ya no puede hacer esto en Python 3 (porque ahora es una función, se ejecuta en su propio ámbito local).

Sin embargo, la irritación no tiene nada que ver con la statement exec , solo se deriva de un detalle de comportamiento especial:

locals() devuelve algo, que quiero llamar “un singleton mutable de scope que, después de la llamada a locals() , siempre hace referencia a todas las variables en el scope local”.

Tenga en cuenta que el comportamiento de locals() no cambió entre Python 2 y 3. Entonces, este comportamiento junto con el cambio de cómo funciona exec parece ser errático, pero no lo es, ya que solo expone algunos detalles, que siempre estuvieron ahí .

¿Qué significa “un singleton mutable de scope que hace referencia a variables en el scope local”?

  • Es un scope-wise singleton , ya que, independientemente de la frecuencia con la que llame a locals() en el mismo scope, el objeto devuelto siempre es el mismo.
    • De ahí la observación, que id(d) == id(locals()) , porque d y locals() refieren al mismo objeto, al mismo singleton, ya que solo puede haber uno (en un ámbito diferente, se obtiene un objeto diferente) , pero en el mismo ámbito solo se ve este único).
  • Es mutable , ya que es un objeto normal, por lo que puede alterarlo.
    • locals() que todas las entradas en el objeto vuelvan a hacer referencia a las variables en el ámbito local.
    • Si cambia algo en el objeto (a través de d ), esto altera el objeto, ya que es un objeto mutable normal.
  • Estos cambios del singleton no se propagan nuevamente al ámbito local, porque todas las entradas en el objeto son references to the variables in the local scope . Entonces, si modifica las entradas, esto cambia el objeto singleton, y no el contenido de donde “las referencias apuntan antes de cambiar la referencia” (por lo tanto, no modifica la variable local).

    • En Python, las cadenas y los números no son mutables. Esto significa que, si asigna algo a una entrada, no cambia el objeto al que apunta la entrada, introduce un nuevo objeto y le asigna una referencia a la entrada. Ejemplo:

       a = 1 d = locals() d['a'] = 300 # d['a']==300 locals() # d['a']==1 

    Además de la optimización esto hace:

    • Cree un nuevo Número de objeto (1), que es otro singleton, BTW.
    • guarde el puntero a este número (1) en LOCALS['a']
      (donde LOCALS será el ámbito local interno)
    • Si aún no existe, crea el objeto SINGLETON
    • actualizar SINGLETON , por lo que hace referencia a todas las entradas en LOCALS
    • almacenar el puntero de SINGLETON en SINGLETON LOCALS['d']
    • Crear un número (300), que no es un singleton, por cierto.
    • guarde el puntero a este número (300) en d['a']
    • Por lo tanto, el SINGLETON se actualiza.
    • pero LOCALS no se actualiza, por lo que la variable local a o LOCALS['a'] aún es Number (1)
    • Ahora, se vuelve a llamar a locals() , se actualiza SINGLETON .
    • Como d refiere a SINGLETON , no a SINGLETON , ¡ d cambia!

Para obtener más información sobre este sorprendente detalle, ¿por qué 1 es un singleton mientras que 300 no lo es? Consulte https://stackoverflow.com/a/306353

Pero, por favor, no olvide: los números son inmutables, por lo que si intenta cambiar un número a otro valor, creará efectivamente otro objeto.

Conclusión:

No puede devolver el comportamiento exec de Python 2 a Python 3 (excepto al cambiar su código), ya que no hay forma de alterar las variables locales fuera del flujo del progtwig.

Sin embargo, puede llevar el comportamiento de Python 3 a Python 2, de modo que, hoy, puede escribir progtwigs que se ejecutan de la misma manera, independientemente de si se ejecutan con Python 3 o Python 2. Esto se debe a que en Python 2 (más reciente) también puede usar exec con función como argumentos (de hecho, es una tupla de 2 o 3), con permite usar la misma syntax con la misma semántica conocida de Python 3:

 exec "code" 

(que solo funciona en Python 2) se convierte (que funciona para Python 2 y 3):

 exec("code", globals(), locals()) 

Pero cuidado, ese "code" no puede alterar más el ámbito local de esta manera. Consulte también https://docs.python.org/2/reference/simple_stmts.html#exec

Algunas últimas palabras:

El cambio de exec en Python 3 es bueno. Debido a la optimización.

En Python 2 no pudo optimizar a través de exec , porque el estado de todas las variables locales que contenían contenidos inmutables podría cambiar de forma impredecible. Esto ya no puede pasar. Ahora las reglas usuales de invocaciones de funciones se aplican a exec() como a todas las demás funciones, también.

Me temo que no puedo explicarlo exactamente, pero básicamente se debe al hecho de que b dentro de la función es local, y parece que exec() asigna al b global. Tendrá que declarar que b es global dentro de la función y dentro de la sentencia exec.

Prueba esto:

 from sys import version print(version) def execute1(a, st): b = 42 exec("b = {}\nprint('b:', b)".format(st)) print(b) def execute2(a, st): global b b = 42 exec("global b; b = {}\nprint('b:', b)".format(st)) print(b) a = 1. execute1(a, "1.E6*a") print() execute2(a, "1.E6*a") print() b = 42 exec("b = {}\nprint('b:', b)".format('1.E6*a')) print(b) 

Lo que me da

 3.3.0 (default, Oct 5 2012, 11:34:49) [GCC 4.4.5] b: 1000000.0 42 b: 1000000.0 1000000.0 b: 1000000.0 1000000.0 

Puede ver que fuera de la función, la b global se recoge automáticamente. Dentro de la función, está imprimiendo el local b.

Tenga en cuenta que habría pensado que exec() siempre usa la b global primero, por lo que en execute2() , no necesita declararlo dentro de la función exec() . Pero encuentro que eso no funciona (que es la parte que no puedo explicar exactamente).