【ITニュース解説】The Scope Investigation: Local, Global, and Nonlocal Variables
2025年10月04日に「Dev.to」が公開したITニュース「The Scope Investigation: Local, Global, and Nonlocal Variables」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonの変数には使える範囲「スコープ」がある。関数内で作るローカル変数は関数内限定。プログラム全体のグローバル変数はglobal宣言で変更できる。ネスト関数ではnonlocalで外側の変数を操作する。変数の検索はLEGB順で行われ、理解が重要だ。
ITニュース解説
システムエンジニアを目指す初心者にとって、プログラムにおける「変数のスコープ」の理解は非常に重要だ。スコープとは、簡単に言えば、プログラムのどの部分から特定の変数にアクセスしたり、変更したりできるかを定める「変数の有効範囲」のことである。データは変数に格納され、関数はこれらのデータを操作する役割を担うが、関数がどのデータにアクセスできるか、あるいはできないかというルールは、プログラムの動作を理解し、正しく記述するために不可欠な基礎となる。
まず、最も基本的な「ローカル変数」の概念から説明する。関数が定義され、実行されるとき、その関数は自分専用のプライベートな作業空間を持つと考えることができる。この空間内で作成された変数は「ローカル変数」と呼ばれる。ローカル変数は、関数が実行されている間だけ存在し、関数がその処理を終えると同時に消滅する。例えば、ある関数が書籍のタイトルを受け取り、棚の場所を割り当てる処理を行うとする。このとき、棚の場所を示す変数は、その関数内でのみ意味を持ち、関数が役割を終えれば、その情報も不要になる。関数外からこのローカル変数にアクセスしようとすると、Pythonは「その変数は定義されていません」というエラーを出す。これは、関数内で作成された変数が、その関数の内部に厳密に閉じ込められていることを意味し、他の部分からは見えないためである。この仕組みによって、各関数は独立性を保ち、他の関数の変数を誤って変更してしまうといった事故を防ぐことができる。
次に、「グローバル変数」について見ていこう。ローカル変数が関数の内部に閉じ込められている一方で、関数は、自分よりも広い範囲で定義された変数、つまりプログラム全体で利用できる「グローバル変数」を読み取ることができる。例えば、図書館全体の蔵書数を管理するグローバル変数があったとして、書籍を追加する関数がこの蔵書数を参照することは可能だ。しかし、注意が必要なのは、関数内でグローバル変数を「読み取る」ことはできるが、そのまま「変更」しようとすると問題が発生する場合がある点だ。ある関数内でグローバル変数と同じ名前の変数に値を代入しようとすると、Pythonはそれをグローバル変数の変更とは見なさず、新しくその関数内だけの「ローカル変数」として扱おうとする。もし、この新しいローカル変数を、それが作成される前に利用しようとすると、「UnboundLocalError」というエラーが発生する。Pythonは、「この変数はローカル変数だと宣言しようとしているが、その前に使われている」と判断するからだ。
この問題を解決し、関数内で明示的にグローバル変数を変更したい場合には、「global」キーワードを使用する。globalキーワードを変数の宣言とともに記述することで、Pythonに「これは新しくローカル変数を作成するのではなく、プログラム全体に存在するあのグローバル変数を変更したいのだ」と伝えることができる。これにより、関数は自身のプライベートな作業空間を超えて、プログラム全体の共通の状態を更新できるようになる。globalキーワードは、プログラム全体に影響を与える変数を扱う際に、その意図を明確にするために使われる重要なツールだ。
さらに複雑なケースとして、「ネストされた関数」、つまり関数の中に別の関数が定義されている状況がある。外側の関数の中にローカル変数があり、その内側の関数が外側の関数のローカル変数を変更しようとするときに、前述のグローバル変数の場合と同じような「UnboundLocalError」が発生することがある。これは、内側の関数から見ると、外側の関数の変数はグローバル変数ではないため、globalキーワードでは対処できない。内側の関数は、外側の関数の変数を読み取ることはできるが、変更しようとすると、やはりそれを自分自身のローカル変数だと誤解してしまうのだ。
このネストされた関数の問題に対処するために、「nonlocal」キーワードが登場する。nonlocalキーワードは、globalキーワードがモジュールレベルのグローバル変数を対象とするのに対し、ネストされた関数において、その「直接一つ外側の関数スコープ」にある変数を変更することを許可する。nonlocalキーワードを変数の宣言とともに記述することで、内側の関数はPythonに「これはローカル変数でもグローバル変数でもなく、私を囲んでいる外側の関数の変数を変更したいのだ」と伝えることができる。これにより、ネストされた関数は、外側の関数の状態を適切に管理し、より複雑な処理を構築することが可能になる。
Pythonが変数を検索する際の規則は、「LEGBルール」として知られている。これは、変数を参照しようとしたときに、Pythonがどのような順序で変数を探すかを示すものだ。LEGBは、「Local(ローカル)」「Enclosing(囲むスコープ)」「Global(グローバル)」「Built-in(組み込み)」の頭文字を取っている。
- Local(ローカル): まず、現在実行中の関数内で定義された変数を探す。
- Enclosing(囲むスコープ): 次に、もし変数がローカルスコープで見つからなかった場合、その関数を囲んでいる一つ外側の関数(ネストされた関数の場合)のスコープを探す。
- Global(グローバル): さらに見つからなかった場合、モジュール全体(Pythonファイル全体)で定義されたグローバル変数を探す。
- Built-in(組み込み): 最後に、Pythonに最初から用意されている組み込み関数や変数(例えば
print関数やlen関数など)の中から探す。 Pythonはこの順序で変数を探し、最初に見つかったものを使用する。この検索順序を理解することは非常に重要で、特に注意すべきは「シャドーイング」だ。これは、外側のスコープに存在する変数と同じ名前の変数を内側のスコープで定義してしまうことで、外側の変数が内側のスコープから見えなくなる現象である。例えば、組み込みのlen関数と同じ名前のlenという変数をグローバルスコープで定義し、さらに内側の関数で別のlen変数を定義した場合、一番内側の関数は自分のローカルなlenだけを認識し、外側のlenや組み込みのlenは隠されてしまう。これにより、意図しない動作やデバッグの困難さにつながる可能性があるため、変数名を適切に選ぶことが推奨される。
これらのスコープの概念は、それぞれの状況で適切に使い分けることが肝要である。ローカル変数は、関数の独立性を保ち、コードの再利用性や読みやすさを高めるための最も一般的なベストプラクティスだ。ほとんどの計算や一時的なデータはローカル変数として扱うべきである。グローバル変数は、プログラム全体で共有される定数(例えば設定値やプログラム名など)や、真にプログラム全体の共通の状態を表す場合に限り、非常に限定的に使用する。なぜなら、グローバル変数を乱用すると、プログラムの様々な部分が互いに依存し合い、コードが複雑で変更しにくくなる「密結合」という問題を引き起こす可能性があるからだ。nonlocal変数は、ネストされた関数において、外側の関数の状態を維持し、内側の関数を通じて更新するといった、より高度なプログラミングパターンで役立つ。
変数のスコープを理解することは、Pythonの関数がどのようにデータを扱い、相互作用するかを深く理解することにつながる。関数が自分自身のプライベートな領域を持ちつつも、必要に応じて外側のリソースにアクセスしたり、変更したりするための厳格なルールがあることを把握するのだ。これにより、予測可能で堅牢なプログラムを構築するための基盤を築くことができる。スコープの原則をマスターすれば、より高度な概念へと進む準備が整うだろう。