web-dev-qa-db-ja.com

Pythonの変数スコープと名前解決

Pythonが変数のスコープや名前解決などを行う方法は基本的に理解していないと思います。特に、以下の関数broken()は実際には機能しませんびっくりしました。そして、私はしばらくの間、役に立つ説明を求めてWebで釣りをしてきましたが、それでもまだわかりません。誰でも、Pythonでこの機能がどのように機能するかについての十分な詳細を説明またはリンクできますか?関連資料を読んだ後、broken()が機能しない理由は明らかだと思いますか?

# Why does this code work fine
def okay0():
    def foo():
        L = []
        def bar():
            L.append(5)
        bar()
        return L
    foo()

# and so does this
def okay1():
    def foo():
        def bar():
            L.append(5)
        L = []
        bar()
        return L
    foo()

# but the following code raises an exception?
def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()

# Example
test_list = [okay0, okay1, broken]
for test_function in test_list:
    try:
        test_function()
    except:
        print("broken")
    else:
        print("okay")
25
goblin

別の関数内で定義された関数は、その親のスコープにアクセスできます。

特定のケースでは、Lは常にfoo()内で定義されます。最初の2つの例では、bar()foo()内で定義されているため、上記のルールでLにアクセスできます(つまり、foo() is bar() 'です)。の親)。

ただし、broken()では、bar()foo()は兄弟です。彼らはお互いのスコープを何も知らないので、bar()Lを見ることができません。

ドキュメント から:

スコープは静的に決定されますが、動的に使用されます。実行中はいつでも、名前空間に直接アクセスできるネストされたスコープが少なくとも3つあります。

  • 最初に検索される最も内側のスコープには、ローカル名が含まれます
  • 最も近い囲みスコープから開始して検索されるすべての囲み関数のスコープには、非ローカル名だけでなく、非グローバル名も含まれます
  • 最後から2番目のスコープには、現在のモジュールのグローバル名が含まれています
  • 最も外側のスコープ(最後に検索)は、組み込み名を含む名前空間です

bar()の後にLがテキストで定義されている場合、なぜ_okay1_が機能するのですか?

Pythonは、実際にコードを実行する必要があるまで、識別子を解決しようとしません(@Giustiの回答で説明されているように、動的バインディング)。

Pythonが関数を実行すると、識別子Lが検出され、ローカルの名前空間で検索されます。cpythonの実装では、これは実際のディクショナリなので、ディクショナリでLという名前のキー。

見つからない場合は、-すべての囲んでいる関数のスコープ、つまり囲んでいる関数のローカル名前空間を表す他の辞書をチェックします。

なお、bar()の後にLdefinedであっても、bar()calledの場合、Lはすでに定義されています。したがって、bar()が実行されたとき、Lはすでにfoo()のローカル名前空間に存在し、Pythonがbar()

ドキュメントのサポート部分:

名前空間は、名前からオブジェクトへのマッピングです。現在、ほとんどの名前空間はPython辞書として実装されていますが、通常は(パフォーマンスを除いて)まったく気付かれず、将来変更される可能性があります。

(...)

関数のローカル名前空間は、関数が呼び出されると作成され、関数が関数内で処理されない例外を返すか発生させると削除されます。 (実際には、忘却のほうが実際に何が起こっているかを説明するのに適しています。)もちろん、再帰呼び出しにはそれぞれ独自のローカル名前空間があります。

スコープは、Pythonプログラムのテキスト領域であり、名前空間に直接アクセスできます。ここでの「直接アクセス可能」とは、名前への非修飾参照が名前空間で名前を検索しようとすることを意味します。

18
caxcaxcoatl

見た目よりシンプルです。

最初のケースはおそらく最も明白です。

_ def okay0():
    def foo():
        L = []
        def bar():
            L.append(5)
        bar()
        return L
    foo()
_

ここにあるのは通常のスコープルールだけです。 Lbarは同じスコープに属し、Lが最初に宣言されます。したがって、bar()Lにアクセスできます。

2番目のサンプルも同様です。

_def okay1():
    def foo():
        def bar():
            L.append(5)
        L = []
        bar()
        return L
    foo()
_

ここでは、Lbar()の両方が同じスコープに属しています。それらはfoo()に対してローカルです。 Pythonは動的バインディングを使用するため、見た目が異なる場合があります。つまり、foo()内の名前Lの解決は、関数が呼び出されたときにのみ解決されます。そのときまでに、PythonはLfoo()を含む同じ関数へのローカル変数であることをすでに知っているので、アクセスは有効です。

ただし、Pythonには動的バインディングがありますが、notには動的スコープがあるため、これは失敗します。

_def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()
_

ここには、Lという名前の2つの変数があります。 1つはfoo()に対してローカルであり、もう1つはbar()に対してローカルです。これらの関数はネストされておらず、Pythonには動的スコープがないため、2つの異なる変数です。bar()Lを割り当てに使用しないため、例外が発生します。

6
giusti

Broken()関数は次のエラーをスローします。

NameError: name 'L' is not defined

これは、Lがfoo()内で定義され、その関数に対してローカルであるためです。 bar()などの他の関数で参照しようとすると、定義されません。

def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()

基本的に、関数内で変数を宣言すると、その関数に対してローカルになります。

2
Calculus

知りたい最も重要な概念はenvironment evaluation model、シンプルですが強力です。

良い 素材 をご紹介しましょう。

Pythonドキュメントを読みたい場合は、 4。実行モデル— Python 3.7.4ドキュメント を読むことができます。簡潔。

名前がコードブロックで使用されている場合、最も近い囲みスコープを使用して解決されます。コードブロックに表示されるそのようなすべてのスコープのセットは、ブロックの環境と呼ばれます。

1
LiuXiMin

fixedで_L = ..._を含む行は、Lのスコープでfixedを宣言します。 (割り当てが実際に実行されないことを確認する前のreturnは、スコープの決定にのみ使用されます。)_nonlocal L_を含む行は、L内のfooが外部スコープのLを参照することを宣言します。この場合、fixedです。それ以外の場合、Lへの割り当てはfoo内に存在するため、L内のfoo変数を参照します。

基本的に:

  • 変数への割り当ては、それを囲んでいる関数にスコープを設定します。
  • nonlocalまたはglobal宣言はスコープをオーバーライドしますが、代わりに(innermost?outermost?)スコープを宣言された変数またはグローバルスコープとともに使用します。
_def fixed():
    def foo():
        nonlocal L  # Added
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()
    return  # Added
    L = ...  # Added
_
1
Solomon Ucko