【ITニュース解説】Understanding Python’s LEGB rule, closures, and why variables sometimes behave like shadows.
2025年09月20日に「Dev.to」が公開したITニュース「Understanding Python’s LEGB rule, closures, and why variables sometimes behave like shadows.」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonの変数スコープはLEGBルールで決まる。関数内の変数はその関数内のみ有効だが、入れ子関数は親の変数を参照可能だ。関数内で変数に代入すると、同名でもそのスコープで新しい変数が作られ、外側とは別物となる。`global`や`nonlocal`で外側の変数を操作する。
ITニュース解説
Pythonにおける変数のアクセス範囲、つまり「スコープ」の仕組みを理解することは、プログラムが期待通りに動作するかを把握する上で非常に重要である。変数はコードのどこからでも自由に使えるわけではなく、その変数がどこで定義されたかによって、どこからアクセスできるかが決まる。この変数の名前をPythonがどのように解決するかを知ることが、複雑なプログラムを読み解く鍵となる。
まず、最も基本的なスコープは「ローカルスコープ」である。これは関数の中で変数を定義した場合に適用される。例えば、greetという関数内でmessageという変数を定義すると、messageはそのgreet関数が実行されている間だけ存在し、関数が終了すると消滅する。そのため、関数の外でmessageにアクセスしようとすると、Pythonはmessageという名前を見つけられず、エラーとなる。変数はその関数の中でのみ有効であり、外からは見えない状態である。
次に、「エンクローシングスコープ」という概念がある。これは関数の中にさらに別の関数が定義されている、いわゆる「入れ子関数」の場合に現れる。例えば、outer_functionの中にinner_functionがあるとき、inner_functionは自分自身のローカルスコープに変数がなければ、その一つ外側にあるouter_functionのスコープに変数を探しに行く。outer_functionでouter_messageという変数を定義していれば、inner_functionはそのouter_messageを参照できる。内側の関数は、外側の関数の変数を参照できるのである。
そして、「グローバルスコープ」が存在する。これはスクリプトのトップレベル、つまりどの関数の中にも入らずに直接定義された変数のことを指す。global_messageのような変数をスクリプトの先頭で定義すると、そのモジュール内のどの関数からもこのglobal_messageにアクセスできる。
最後に、「組み込みスコープ」がある。これはPythonが言語として最初から提供している、print関数やlen関数、True、False、Noneといった基本的な名前が格納されている場所である。これらの名前は、特別な宣言なしに常に利用可能である。
Pythonが変数名を探す際には、これら四つのスコープを特定の順番で探す規則がある。これを「LEGBルール」と呼ぶ。
- Local(ローカルスコープ):現在実行中の関数の内部
- Enclosing(エンクローシングスコープ):もし関数が入れ子になっている場合、その外側の関数
- Global(グローバルスコープ):モジュール(スクリプトファイル)のトップレベル
- Built-in(組み込みスコープ):Pythonに組み込まれている名前
Pythonは、この順番で変数名を探し、最初に見つかった場所で探索を停止する。
このスコープの仕組みが少し複雑になるのは、変数への「代入」が絡む場合である。Pythonでは、あるスコープ内で変数に値を代入する文(例:x = 10)があると、原則としてその変数を現在のスコープのローカル変数として扱う。つまり、外側のスコープに同名の変数があっても、関数内で代入を行うとその関数独自の新しい変数が作られ、外側の変数は変更されない。
例えば、グローバルスコープにshadow = 10という変数があり、outer_lampという関数の中でshadow = 20と代入すると、このshadow = 20はouter_lamp関数内のローカル変数となる。そして、outer_lampの中に入れ子のinner_lamp関数があり、inner_lampがshadowを参照する場合、LEGBルールに従って、まず自身のローカルスコープを探し、次にエンクローシングスコープであるouter_lampのスコープを探す。ここでshadow = 20が見つかるため、inner_lampは20を表示する。このとき、グローバルのshadow = 10は全く変更されず、そのまま残る。このように、内側のスコープで定義された変数が、外側の同名変数を「隠蔽」する現象を理解することが重要である。
さらに注意が必要なケースとして、関数内で変数に代入が行われるものの、その代入の前に同じ変数を参照しようとした場合に発生するUnboundLocalErrorがある。例えば、innerという関数内でprint(shadow)の後にshadow = "local"という代入文があるとする。Pythonは、関数内にshadowへの代入があるのを見ると、そのshadowはinner関数のローカル変数であると判断する。しかし、代入文が実行される前にprint(shadow)で参照しようとすると、ローカル変数shadowにはまだ値が割り当てられていないため、「ローカル変数'shadow'が代入前にアクセスされた」というエラーが発生する。関数内で一度でも代入が行われる変数は、その関数内ではローカル変数として扱われるというルールを覚えておく必要がある。
もし、関数の中から外側のスコープにある変数を参照するだけでなく、その値を変更したい場合は、Pythonにその意図を明示的に伝える必要がある。
グローバルスコープの変数を変更したい場合は、関数内でglobalキーワードを使用する。例えば、global shadowと宣言してからshadow = 10と代入すると、これはグローバルスコープにあるshadowを変更する操作となる。
入れ子関数において、内側の関数から直接の外側関数(エンクローシングスコープ)の変数を変更したい場合は、nonlocalキーワードを使用する。nonlocal shadowと宣言することで、現在の関数の一つ外側のスコープにあるshadowを変更できる。nonlocalを使わずに代入を行うと、新たなローカル変数が作成されてしまうため注意が必要である。
スコープの理解は、単にエラーを避けるためだけでなく、プログラムの設計において非常に重要である。変数がどの範囲で有効で、いつ、どのように変更されるかを正確に把握することで、予期せぬバグを防ぎ、コードをより予測可能で保守しやすいものにできる。変数の影響範囲がどこまで届くのかを知ることは、Pythonプログラミングの基礎となる。