4

Python で exec ステートメントまたは execfile() を使用してスクリプトを実行した場合の NameError 例外に関する既存の質問をいくつか調べましたが、次の動作の適切な説明はまだ見つかりませんでした。

実行時に execfile() を使用してスクリプト オブジェクトを作成する単純なゲームを作成したいと考えています。以下は、この問題を示す 4 つのモジュールです (ご容赦ください。これは私ができる限り簡単です!)。メイン プログラムは、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()

スクリプト ファイルは、サウンドを再生するオブジェクトを作成し、そのオブジェクトをスクリプト マネージャーのリストに追加するだけです。

 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)

スクリプト マネージャーは、各スクリプトの action() 関数を呼び出すだけです。

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

gamelib 関数は 4 番目のモジュールで定義されています。これは、アクセスするのが面倒なモジュールです。

# gamelib.py

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

上記のコードは、次の出力で機能します。

mhack:exec $ python game.py
ScriptObject.action(): gamelib.play_sound() の呼び出し
ブーム!
mhack:exec $

ただし、game.py の「import gamelib」ステートメントをコメントアウトし、script.py の「import gamelib」のコメントを外すと、次のエラーが発生します。

mhack:exec $ python game.py
ScriptObject.action(): gamelib.play_sound() の呼び出し
トレースバック (最新の呼び出しが最後):
  ファイル「game.py」の 10 行目
    主要()
  ファイル「game.py」、8 行目、メイン
    script_mgr.run()
  ファイル "/Users/williamknight/proj/test/python/exec/script_mgr.py"、12 行目、実行中
    obj.action()
  ファイル「script.py」、9 行目、動作中
    gamelib.play_sound()
NameError: グローバル名 'gamelib' が定義されていません

私の質問は次のとおりです。1)スクリプトを実行する「game.py」モジュールでインポートが必要なのはなぜですか? 2) 「gamelib」が参照されているモジュール (script.py) または呼び出されているモジュール (script_mgr.py) から「gamelib」をインポートできないのはなぜですか?

これは Python 2.5.1 で発生します

4

2 に答える 2

3

execfileのPython ドキュメントから:

execfile(ファイル名[, グローバル[, ローカル]])

locals ディクショナリを省略すると、デフォルトで globals ディクショナリになります。両方の辞書を省略した場合、式は execfile() が呼び出された環境で実行されます。

execfile には 2 つのオプションの引数があります。両方を省略しているため、スクリプトは execfile が呼び出される環境で実行されています。したがって、game.py のインポートが動作を変更する理由です。

さらに、game.py と script.py でのインポートの次の動作を結論付けました。

  • game.py ではimport gamelib、gamelib モジュールをglobals と locals の両方にインポートします。これは script.py に渡される環境です。これが、gamelib が ScriptObject アクション メソッド (グローバルからアクセス) でアクセスできる理由です。

  • script.py ではimport gamelib、gamelib モジュールをローカルのみにインポートします(理由はわかりません)。そのため、グローバルから ScriptObject アクション メソッドから gamelib にアクセスしようとすると、NameError が発生します。次のようにインポートをアクション メソッドのスコープに移動すると機能します (gamelib はローカルからアクセスされます)。

    class ScriptObject:
        def action(self):
            import gamelib
            print("ScriptObject.action(): calling gamelib.play_sound()")
            gamelib.play_sound()
    
于 2010-02-27T19:42:57.237 に答える
0

script.py の「import gamelib」が効果がない理由は、game.py main() のローカル スコープにインポートするためです。これは、インポートが実行されるスコープであるためです。このスコープは、実行時に ScriptObject.action() の可視スコープではありません。

globals() と locals() の変更を出力するデバッグ コードを追加すると、次の変更されたバージョンのプログラムで何が起こっているかがわかります。

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

プログラムのデバッグ出力は次のとおりです。

--- game(): BEF exec: グローバル:
0000 __ビルトイン__:
0001 __doc__: なし
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: BEF main()
0005 ゲームライブラリ:
0006 メイン:
0007 report_dict:
0008 script_mgr:

--- game(): BEF exec: ローカル:

--- game(): AFT exec: グローバル:
0000 __ビルトイン__:
0001 __doc__: なし
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: in main()、AFT execfile()
0005 ゲームライブラリ:
0006 メイン:
0007 report_dict:
0008 script_mgr:

--- game(): AFT exec: locals:
0000 ScriptObject: __main__.ScriptObject
0001 ゲームライブラリ:
0002 オブジェクト:
0003 pdb:
0004 script_mgr:

--- ScriptObject.action(): グローバル:
0000 __ビルトイン__:
0001 __doc__: なし
0002 __file__: game.py
0003 __name__: __main__
0004 _game_global: in main()、AFT execfile()
0005 ゲームライブラリ:
0006 メイン:
0007 report_dict:
0008 script_mgr:

--- ScriptObject.action(): ローカル:
0000 report_dict:
0001 自分:


ブーム!

import を game.py または script.py のモジュール レベルに配置しようとする代わりに、スクリプト オブジェクト メンバー関数のローカル スコープに import ステートメントを配置するという Yukiko の提案に従います。これは私には少しぎこちなく思えます。exec されたスクリプトに対してそのようなインポートを指定するためのより良い方法があるかもしれませんが、少なくとも何が起こっているのか理解できました。

于 2010-02-28T16:21:22.063 に答える