Pregunta

Me miraba a una serie de preguntas existentes sobre excepciones NameError cuando los scripts se ejecutan con las declaraciones Exec o execfile () en Python, pero no han encontrado todavía una buena explicación de la siguiente comportamiento.

Quiero hacer un juego simple que crea objetos en tiempo de ejecución de secuencias de comandos con execfile (). A continuación se presentan 4 módulos que demuestran el problema (por favor tengan paciencia conmigo, esto es tan simple como podría hacerlo!). El programa principal sólo carga un script utilizando execfile () y luego llama a un gestor de secuencia de comandos para ejecutar los objetos de secuencia de comandos:

# game.py

import script_mgr
import gamelib  # must be imported here to prevent NameError, any place else has no effect

def main():
  execfile("script.py")
  script_mgr.run()

main()

El archivo de script sólo crea un objeto que reproduce un sonido y luego se añade el objeto a una lista en el gestor de secuencia de comandos:

 script.py

import script_mgr
#import gamelib # (has no effect here)

class ScriptObject:
  def action(self):
    print("ScriptObject.action(): calling gamelib.play_sound()")
    gamelib.play_sound()

obj = ScriptObject()
script_mgr.add_script_object(obj)

El director de la escritura sólo llama a la función de acción () de cada secuencia de comandos:

# script_mgr.py

#import gamelib # (has no effect here)

script_objects = []

def add_script_object(obj):
  script_objects.append(obj)

def run():
  for obj in script_objects:
    obj.action()

La función gamelib se define en un cuarto módulo, que es el molesto para ser visitada:

# gamelib.py

def play_sound():
  print("boom!")

El código anterior funciona con el siguiente resultado:

mhack:exec $ python game.py
ScriptObject.action(): calling gamelib.play_sound()
boom!
mhack:exec $ 

Sin embargo, si el comentario de una declaración de importación gamelib 'en game.py y quite el comentario 'gamelib importación' en script.py, me sale el siguiente error:

mhack:exec $ python game.py
ScriptObject.action(): calling gamelib.play_sound()
Traceback (most recent call last):
  File "game.py", line 10, in 
    main()
  File "game.py", line 8, in main
    script_mgr.run()
  File "/Users/williamknight/proj/test/python/exec/script_mgr.py", line 12, in run
    obj.action()
  File "script.py", line 9, in action
    gamelib.play_sound()
NameError: global name 'gamelib' is not defined

Mi pregunta es: 1) ¿Por qué es necesaria la importación en el módulo 'game.py', la que los ejecutivos de la secuencia de comandos? 2) ¿Por qué no funciona para importar 'gamelib' desde el módulo en el que se hace referencia (script.py) o el módulo donde se le llama (script_mgr.py)?

Esto ocurre en Python 2.5.1

¿Fue útil?

Solución

Desde el Python documentación para execfile:

execfile (filename [, [globales, locales]])

Si se omite el diccionario gente toma por defecto las variables globales diccionario. Si se omiten los dos diccionarios, la expresión se ejecuta en el entorno donde execfile () es llamado.

Hay dos argumentos opcionales para execfile. Dado que se omite a los dos, la secuencia de comandos se ejecuta en el entorno donde execfile se llama. De ahí la razón de la importación en game.py cambia el comportamiento.

Además, I concluyó el siguiente comportamiento de la importación en game.py y script.py:

  • En las importaciones import gamelib game.py el módulo gamelib en ambas variables globales y locales . Este es el entorno pasado a script.py que es la razón por gamelib es accesible en el método de acción ScriptObject (se accede desde globales).

  • En las importaciones import gamelib script.py el módulo gamelib en locales solamente (no estoy seguro de la razón). Así que cuando se trata de acceso gamelib del método de acción ScriptObject de variables globales que tiene la NameError. Se trabajará si se mueve la importación en el ámbito del método de acción de la siguiente manera (gamelib se accede desde los locales):

    class ScriptObject:
        def action(self):
            import gamelib
            print("ScriptObject.action(): calling gamelib.play_sound()")
            gamelib.play_sound()
    

Otros consejos

La razón por la 'gamelib importación' en script.py no tiene ningún efecto se debe a que las importaciones en el ámbito local de la principal game.py (), porque este es el ámbito en el que se ejecuta la importación. Este alcance no es un ámbito visible para ScriptObject.action () cuando se ejecuta.

La adición de código de depuración para imprimir los cambios en las variables globales y locales () () revela lo que está pasando en la siguiente versión modificada del programa:

# game.py

import script_mgr
import gamelib  # puts gamelib into globals() of game.py

# a debug global variable 
_game_global = "BEF main()" 

def report_dict(d):
  s = ""
  keys = d.keys()
  keys.sort() 
  for i, k in enumerate(keys):
    ln = "%04d %s: %s\n" % (i, k, d[k])
    s += ln
  return s

def main():
  print("--- game(): BEF exec: globals:\n%s" % (report_dict(globals())))
  print("--- game(): BEF exec: locals:\n%s" % (report_dict(locals())))
  global _game_global 
  _game_global = "in main(), BEF execfile()"
  execfile("script.py")
  _game_global = "in main(), AFT execfile()"
  print("--- game(): AFT exec: globals:\n%s" % (report_dict(globals())))
  print("--- game(): AFT exec: locals:\n%s" % (report_dict(locals())))
  script_mgr.run()

main()
# script.py 

import script_mgr
import gamelib  # puts gamelib into the local scope of game.py main()
import pdb # a test import that only shows up in the local scope of game.py main(). It will _not_ show up in any visible scope of ScriptObject.action()!

class ScriptObject:
  def action(self):
    def report_dict(d):
      s = ""
      keys = d.keys()
      keys.sort()
      for i, k in enumerate(keys):
        ln = "%04d %s: %s\n" % (i, k, d[k])
        s += ln
      return s
    print("--- ScriptObject.action(): globals:\n%s" % (report_dict(globals())))
    print("--- ScriptObject.action(): locals:\n%s" % (report_dict(locals())))
    gamelib.play_sound()

obj = ScriptObject()
script_mgr.add_script_object(obj)

Este es el resultado de la depuración del programa:

--- game(): BEF exec: globals:
0000 __builtins__: 
0001 __doc__: None
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: BEF main()
0005 gamelib: 
0006 main: 
0007 report_dict: 
0008 script_mgr: 

--- game(): BEF exec: locals:

--- game(): AFT exec: globals:
0000 __builtins__: 
0001 __doc__: None
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: in main(), AFT execfile()
0005 gamelib: 
0006 main: 
0007 report_dict: 
0008 script_mgr: 

--- game(): AFT exec: locals:
0000 ScriptObject: __main__.ScriptObject
0001 gamelib: 
0002 obj: 
0003 pdb: 
0004 script_mgr: 

--- ScriptObject.action(): globals:
0000 __builtins__: 
0001 __doc__: None
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: in main(), AFT execfile()
0005 gamelib: 
0006 main: 
0007 report_dict: 
0008 script_mgr: 

--- ScriptObject.action(): locals:
0000 report_dict: 
0001 self: 


boom!

En lugar de tratar de poner las importaciones en game.py o el nivel de módulo de script.py, voy a seguir la sugerencia de Yukiko poner las declaraciones de importación en el ámbito local de las funciones miembro objeto de script. Esto parece un poco incómodo para mí, y puede haber alguna forma mejor especificar tales importaciones para los scripts exec'd, pero al menos ahora entiendo lo que está pasando.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top