Why doesn't import prohibit using NameError in python script with execfile ()?

I looked at a number of existing questions about NameError exceptions when scripts are run with exec or execfile () statements in Python, but have not yet found a good explanation for the following behavior.

I want to create a simple game that creates script objects at runtime using execfile (). Below are 4 modules that demonstrate the problem (please take me, it is as simple as I could do it!). The main program simply loads the script using execfile () and then calls the script manager to run the script objects:

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

The script file simply creates an object that plays the sound, and then adds the object to the list in the script manager:

  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) 

The script manager simply calls the action () function for each 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() 

The gamelib function is defined in the fourth module, which is problematic for access:

 # gamelib.py def play_sound(): print("boom!") 

The above code works with the following output:

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

However, if I comment out the import gamelib statement in game.py and uncomment the import gamelib statement in script.py, I get the following 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 

My question is: 1) Why is import needed in the "game.py" module that executes the script? 2) Why doesn’t it work to import "gamelib" from the module to which it refers (script.py) or the module where it is called (script_mgr.py)?

This happens on Python 2.5.1

+4
source share
2 answers

From the Python documentation for execfile:

execfile (filename [, globals [, locals]])

If the locals dictionary is omitted, the global dictionary is used by default. If both dictionaries are omitted, the expression is executed in the environment where execfile () is called.

There are two optional arguments to execfile. Since you omit both of them, your script runs in the environment where execfile is called. Therefore, the reason for importing into game.py changes the behavior.

In addition, I completed the following import behavior in game.py and script.py:

  • In game.py import gamelib module is imported into both global and local . This is the environment passed to script.py, so gamelib is available in the ScriptObject action method (accessed from global variables).

  • In script.py import gamelib imports the import gamelib module only into local ones only (not sure about the reason). Therefore, trying to access gamelib from the ScriptObject action method from global variables, you have a NameError. It will work if you move the import to the scope of the action method as follows (access to the gamelib will be from local residents):

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

The reason “import gamelib” in script.py is not affected is because it imports game.py main () into the local area, because it is the area in which the import is performed. This scope is not the visible scope for ScriptObject.action () when it is executed.

Adding debugging code to print the changes to globals () and locals () shows what happens in the following modified version of the program:

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

Here is the debug output of the program:

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

Instead of importing into game.py or at the module level of script.py, I will follow Yukiko's recommendations for placing import statements in the local area of ​​the member functions of the script object. This seems a little inconvenient to me, and there might be some better way to specify such an import for exec'd scripts, but at least now I understand what is going on.

0
source

Source: https://habr.com/ru/post/1302613/


All Articles