Python が「解釈された」言語であることを考慮すると、変数が外側のスコープで既に定義されている場合、同じ変数は内側のスコープでもアクセスできるはずであると自然に想定されます。実際、最初の例では、内側が_func
単に印刷x
されるだけで機能します。
ただし、Python について明らかでないことは、変数のスコープが正確に決定される方法ではないということです。Python はコンパイル時に、変数がそのスコープ内で割り当てられているかどうかに基づいて、スコープに対して「ローカル」と見なすべき変数を分析します。この場合、代入には増補代入演算子が含まれます。したがって、Python_func
が 2 番目の例のインナーをバイトコードにコンパイルするとき、 を見て、それがへのローカル変数である必要があるx += 1
と判断します。もちろん、最初の代入は増補代入であるため、ローカルに増補する変数はなく、.x
_func
x
UnboundLocalError
これを別の方法で確認するには、次のように記述します。
def _func():
print x
x = 2
ここでも、外部関数で定義されたものとしてではなく、その関数のスコープ内のローカル変数として扱う_func
行が含まれているためです。したがって、も になります。x = 2
x
x
print x
UnboundLocalError
dis
モジュールを使用して関数用に生成されたバイトコードを表示することで、これをより詳細に調べることができます。
>>> dis.dis(_func)
2 0 LOAD_FAST 0 (x)
3 PRINT_ITEM
4 PRINT_NEWLINE
3 5 LOAD_CONST 1 (2)
8 STORE_FAST 0 (x)
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
オペコードは、LOAD_FAST
低速でより一般的な名前のルックアップをバイパスするローカル変数の「高速」ルックアップを目的としています。辞書検索などを行うのではなく、各ローカル変数 (現在のスタック フレーム内) がその配列内のインデックスに関連付けられているポインターの配列を使用します。LOAD_FAST
上記の例では、オペコードへの唯一の引数は --0
この場合、最初の (そして唯一の) ローカルです。
関数自体 (具体的にはその基になるコード オブジェクト) で、そのコードで使用されているローカル変数が 1 つあり、それに関連付けられている変数名が次のとおりであることを確認できます'x'
。
>>> _func.__code__.co_nlocals
1
>>> _func.__code__.co_varnames
('x',)
それdis.dis
が報告できる方法です0 LOAD_FAST 0 (x)
。STORE_FAST
後のオペコードについても同様です。
Python で変数のスコープを理解するためにこれを知る必要はありませんが、内部で何が起こっているかを知ることは役に立ちます。
他のいくつかの回答で既に述べたように、Python 3nonlocal
は、ローカル スコープ内のその変数への割り当てに基づいて、このコンパイル時の名前のローカル変数へのバインドを防止するキーワードを導入しました。