2010-02-27 20 views
4

J'ai regardé un certain nombre de questions existantes sur les exceptions NameError lorsque les scripts sont exécutés avec des instructions exec ou execfile() en Python, mais n'ont pas encore trouvé une bonne explication du comportement suivant. Je veux faire un jeu simple qui crée des objets de script à l'exécution avec execfile(). Voici 4 modules qui démontrent le problème (s'il vous plaît, gardez-moi, c'est aussi simple que je pourrais le faire!). Le programme principal charge juste un script en utilisant execfile() et appelle ensuite un gestionnaire de script à exécuter les objets de script:Pourquoi l'importation n'interdit-elle pas NameError dans un script python exécuté avec execfile()?

# 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()

Le fichier script crée simplement un objet qui joue un son et ajoute l'objet à une liste le gestionnaire de script:

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)

le gestionnaire de script appelle simplement la fonction de chaque action script():

# 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 fonction gamelib est définie dans un quatrième module, qui est une problématique à accéder:

# gamelib.py 

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

Le code ci-dessus fonctionne avec la sortie suivante:

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

Cependant, si je en commentaire la mention « gamelib importation » dans game.py et décommenter la 'gamelib d'importation' dans script.py, je reçois l'erreur suivante:

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

Ma question est: 1) Pourquoi l'importation nécessaire dans le module 'game.py', celui qui execs le script? 2) Pourquoi cela ne fonctionne-t-il pas d'importer 'gamelib' du module où il est référencé (script.py) ou du module où il est appelé (script_mgr.py)?

Cela se produit sur Python 2.5.1

Répondre

3

De l'Python documentation pour execfile:

execfile (nom de fichier [, GLOBALS [, les habitants]])

Si le dictionnaire de la population locale est omis il est par défaut dans le dictionnaire global. Si les deux dictionnaires sont omis, l'expression est exécutée dans l'environnement où execfile() est appelé.

Il existe deux arguments facultatifs pour execfile. Puisque vous les omettez tous les deux, votre script est exécuté dans l'environnement où execfile est appelé. D'où la raison pour laquelle l'import dans game.py change le comportement.

En outre, je conclus le comportement suivant l'importation dans game.py et script.py:

  • En game.py import gamelib importe le module gamelib dans les deux et les habitants GLOBALS. C'est l'environnement passé à script.py qui est la raison pour laquelle gamelib est accessible dans la méthode d'action ScriptObject (accessible depuis globals).

  • En script.py import gamelib importe le module gamelib en locaux seulement (pas sûr de la raison). Ainsi, lorsque vous essayez d'accéder à gamelib à partir de la méthode d'action ScriptObject à partir de globals, vous avez le NameError. Il ne fonctionnera que si vous déplacez l'importation dans le cadre de la méthode d'action comme suit (gamelib sera accessible à partir de la population locale):

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

Je connaissais les arguments globals et locals, mais je ne sais toujours pas comment les utiliser au mieux. Votre citation sur l'environnement m'aide à comprendre un peu mieux, mais je ne vois toujours pas pourquoi l'importation dans script.py ne fonctionne pas - cela ne le mettrait-il pas aussi dans l'environnement? –

+0

Mis à jour ma réponse après quelques tests en imprimant des globals et des locaux. J'espère que ça aide;) – Yukiko

+0

Oui, ça aide! Après vos observations sur les effets sur les globals et les locaux des importations, j'ai ajouté un peu de code de débogage pour imprimer les globals et les locaux et cela clarifie vraiment les choses maintenant. J'accepterai votre réponse, mais aussi poster une réponse de suivi avec mes résultats. –

0

La raison pour laquelle le « de gamelib d'importation » en script.py n'a pas d'effet est parce qu'il importe dans la portée locale de game.py main(), parce que c'est la portée dans laquelle l'importation est exécutée. Cette portée n'est pas une portée visible pour ScriptObject.action() lors de son exécution.

Ajout de code de débogage pour imprimer les changements dans les globals() et les habitants() révèle ce qui se passe dans la suite version modifiée du programme:

# 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)

est ici la sortie de débogage du programme:

--- 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!

au lieu d'essayer de mettre les importations en game.py ou au niveau du module de script.py, je vais suivre la suggestion de Yukiko de mettre des déclarations d'importation à la portée locale du script fonctions membres d'objet. Cela me semble un peu gênant, et il pourrait y avoir une meilleure façon de spécifier de telles importations pour les scripts exec'd, mais au moins je comprends maintenant ce qui se passe.

+0

Maintenant, j'ai trouvé un meilleur moyen que de faire des importations dans la portée locale des fonctions membres, je viens de spécifier un env en faisant: execfile ("script.py", script_mgr.env()) où script_mgr.env() renvoie juste le dict global() pour le module script_mgr. Cela fournit un environnement pour les importations script.py qui restent dans la portée. –