Pergunta

Eu olhei para várias perguntas existentes sobre exceções de NameError quando os scripts são executados com instruções executivas ou execfile () no Python, mas ainda não encontrei uma boa explicação do comportamento a seguir.

Quero fazer um jogo simples que cria objetos de script no tempo de execução com o Execfile (). Abaixo estão 4 módulos que demonstram o problema (por favor, tenha paciência comigo, isso é o mais simples possível!). O programa principal apenas carrega um script usando o Execfile () e depois chama um gerenciador de scripts para executar os objetos de script:

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

O arquivo de script apenas cria um objeto que reproduz um som e depois adiciona o objeto a uma lista no gerenciador 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)

O gerente de script apenas chama a função Action () de cada 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()

A função gamelib é definida em um quarto módulo, que é o problemático a ser acessado:

# gamelib.py

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

O código acima funciona com a seguinte saída:

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

No entanto, se eu comentar a declaração 'Importar gamelib' em game.py e descomentar o 'importação gamelib' no script.py, recebo o seguinte erro:

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

Minha pergunta é: 1) Por que a importação é necessária no módulo 'game.py', aquele que executa o script? 2) Por que não funciona para importar 'gamelib' do módulo em que é referenciado (script.py) ou o módulo onde é chamado (script_mgr.py)?

Isso acontece no Python 2.5.1

Foi útil?

Solução

De Documentação do Python para execfile:

Execfile (nome do arquivo [, Globals [, moradores]])

Se o dicionário local for omitido, ele padrão no Globals Dictionary. Se ambos os dicionários forem omitidos, a expressão será executada no ambiente em que o Execfile () é chamado.

Existem dois argumentos opcionais para o Execfile. Como você omita os dois, seu script está sendo executado no ambiente em que o Execfile é chamado. Daí a razão pela qual a importação em game.py muda o comportamento.

Além disso, concluí o seguinte comportamento da importação em game.py e script.py:

  • Em game.py import gamelib importa o módulo gamelib para globais e habitantes locais. Este é o ambiente passado para o script.py, e é por isso que o gamelib está acessível no método de ação do ScriptObject (acessado a partir de globais).

  • Em script.py import gamelib importa o módulo gamelib para somente locais (não tenho certeza do motivo). Portanto, ao tentar acessar o gamelib a partir do método de ação do ScriptObject a partir de globais, você tem o nome NameError. Funcionará se você mover a importação para o escopo do método de ação da seguinte forma (Gamelib será acessado dos habitantes locais):

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

Outras dicas

A razão pela qual o 'importação gamelib' no script.py não tem efeito é porque importa para o escopo local do game.py main (), porque esse é o escopo em que a importação é executada. Esse escopo não é um escopo visível para scriptObject.action () quando executa.

Adicionar código de depuração para imprimir as alterações no Globals () e no local () revela o que está acontecendo na versão modificada a seguir do 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)

Aqui está a saída de depuração do 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!

Em vez de tentar colocar as importações em game.py ou o nível do módulo de script.py, seguirei a sugestão de Yukiko para colocar declarações de importação no escopo local das funções do membro do objeto de script. Isso me parece um pouco estranho, e pode haver uma maneira melhor de especificar essas importações para scripts executivos, mas pelo menos agora entendo o que está acontecendo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top