7

重複の可能性:
Python のネストされた関数の変数スコープ

以前にデコレータを使用したことがあるので、コードにバグがあることに驚きました。

def make_handler(name, panels):
    def get(self):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels = zip(ndb.get_multi(keys), panels)
        panels = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels]
        templates = {'panels': panels, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': get})

"""
Traceback (most recent call last):
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1536, in __call__
    rv = self.handle_exception(request, response, e)
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1530, in __call__
    rv = self.router.dispatch(request, response)
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "C:\Program Files\Google\google_appengine\lib\webapp2\webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "C:\Users\Robert\PycharmProjects\balmoral_doctors\main.py", line 35, in get
    keys = [ndb.Key('Panel', panel) for panel in panels]
UnboundLocalError: local variable 'panels' referenced before assignment
"""

panel私の修正は、最初の使用法をpanel2超えて変更することです:

def make_handler(name, panels):
    def get(self):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels2 = zip(ndb.get_multi(keys), panels)
        panels2 = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels2]
        templates = {'panels': panels2, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': get})

現在、すべてが正常に機能しており、その理由が気になります。

これは私が推測することですが、わかりません:

パネル = zip(..)

パネルがローカル変数であることを意味します。これは、関数がパネルの外側のスコープを調べないことを意味します。

これは get() 関数が実行される前に行われ、途中ではありませんか?

最初に外側の関数からパネルを取得し、その後内側の関数でパネルが定義されると、それ以降は新しいローカル パネル変数を使用すると考えました。

私は正しい軌道に乗っていますか?

4

5 に答える 5

5

あなたは多かれ少なかれ正しく、正しい解決策を見つけました。あなたの問題はこれと同等です:

bars = range(10)

def foo():
    thing = [x for x in bars]
    bars = 'hello'

foo()
# UnboundLocalError: local variable 'bars' referenced before assignment

関数の定義bars時に、がローカル スコープであると判断されます。そして、関数の実行時に、バーが割り当てられていないという問題が発生します。

于 2012-11-08T05:35:48.880 に答える
5

はい。

Python のスコープ規則は、関数が新しいスコープ レベルを定義し、名前がスコープ レベル内の 1 つのスコープ レベルのみの値にバインドされることを示します。つまり、静的にスコープされます (つまり、すべてのスコープはコンパイル時に決定されます)。ご理解のとおり、非ローカル宣言から読み取り、ローカル変数に書き込むことで、これに違反しようとしています。あなたが観察するように、インタープリターは を発生させることによってこれに激しく反対します: それはそれがローカルUnboundLocalError変数であることを理解しています (同時にそれと非ローカルになることはできないため)、しかし、あなたは割り当てられていません (バインドされていません)値を名前に追加するため、失敗します。panels

技術的な詳細

変数がバイトコードのコンパイル時にどこにあるかを追跡するために Python で決定が行われました (このケースに固有の場合、get.__code__.co_varnamesローカル変数のタプルにあります)。つまり、変数は単一のスコープ レベルでのみ使用できます。特定の範囲。Python 2.x では、非ローカル変数を変更することはできません。グローバル変数または非ローカル変数への読み取り専用アクセス、globalステートメントを使用したグローバル変数への読み取り/書き込みアクセス、またはローカル変数への読み取り/書き込みアクセス (デフォルト) のいずれかを持っています。それが設計された方法です (おそらくパフォーマンスと純度のため)。Python 3 では、nonlocalと同様の効果を持つステートメントが導入されましたglobalが、中間のスコープです。

この場合、変更された変数を別の名前にバインドすることが正しい解決策です。

于 2012-11-08T05:36:27.040 に答える
1

多くの人はこれに気づいていませんが、Python は実際にはstatically scopedです。Python が裸の名前 (つまり、オブジェクトの属性ではない) からの読み取りを確認すると、コンパイル時の分析だけで、その名前の読み取り先がどこにあるのかを正確に判断できます。

関数内で名前が割り当てられる場合、その名前はその関数内のローカル変数1であり、そのスコープは関数本体全体、さらには割り当ての前の行にまで及びます。

名前が関数に割り当てられていない場合、その名前は非ローカル変数です。Python は、周囲のdefブロックの静的スコープをチェックして、それらのいずれかのローカル変数であるかどうかを確認できます。そうでない場合、その名前はモジュールのグローバルまたは組み込みへの参照でなければなりません (グローバルと組み込みの選択は動的に解決されるため、動的に宣言されたグローバルで組み込みをシャドウできます)。

これは主に効率的に行われていると思います。これは、関数のバイトコードがコンパイルされたときに関数のローカル変数のセットを知ることができ、ローカル変数へのアクセスを単純なインデックス操作に変換できることを意味します。そうしないと、Python はローカル変数にアクセスするために辞書検索を実行する必要があり、これは遅くなります。

したがって、get関数にはフォームの行が含まれているpanels = ...ためpanels、の本体全体がローカル変数になりますget。への割り当てkeysは、割り当てられる前にローカル変数をループしpanelsています。


1globalその名前がまたは宣言されていない限り、それはnonlocalまだ静的に認識されています。

于 2012-11-08T05:38:38.067 に答える
0

Pythonコンパイラは、最初に関数内のコード全体をスキャンして、ローカル変数のセットを決定します(プラメータとして渡されるか、関数内に新しい値が割り当てられます)。そのため、割り当て前に関数内で新しい値が割り当てられた変数を使用することはできません。

コンパイラーがそれを行わなかった場合、関数のクロージャーを作成することは不可能でした。クロージャには、すべてのフリー変数または非ローカル変数への参照が必要です。varが関数の一部に無料で、他の部分にローカルである場合....それは意味がありません:)

于 2012-11-08T05:53:26.973 に答える
0

変数が関数内で割り当てられている場合、明示的に宣言しない限り、globalまたは Python 3.x ではnonlocal. グローバルに宣言されている場合、変数はモジュールのグローバルで定義されている必要があり、上記のケースはカバーされていません。Python 2.x ソリューションを 1 つ見つけました。panelsもう1つは、引数として追加しgetて使用することfunctools.partialです:

def make_handler(name, panels):
    def get(self, panels):
        admin = True
        keys = [ndb.Key('Panel', panel) for panel in panels]
        panels = zip(ndb.get_multi(keys), panels)
        panels = [(panel.panel_html if panel else get_default_content(panel_id), panel_id) for panel, panel_id in panels]
        templates = {'panels': panels, 'admin': admin}
        self.render_template('panel_page.html', **templates)
    return type(name, (BaseHandler,), {'get': functools.partial(get, panels=panels)})

関連項目: Python のクロージャPython 非ローカル ステートメント

于 2012-11-08T05:35:42.410 に答える