関数の名前空間とスコープ
このトピックを修了すると
Pythonが変数名をどのような順序で検索するか(LEGBルール)を説明でき、ローカル変数とグローバル変数が競合する状況を適切に処理できるようになります。
同じ名前、異なる値
x = 10
def foo(): x = 20 print(x)
foo() # 20print(x) # 10関数の内部のxと、外部のxは、名前は同じですが、異なる変数です。これを理解するためには、名前空間とスコープを知る必要があります。
名前空間とは
名前空間(namespace)は、名前とオブジェクトのマッピングを格納する空間です。Pythonでは、辞書のように{名前: オブジェクト}の形式で動作します。
x = 10name = "철수"# モジュール名前空間: {"x": 10, "name": "철수", ...}def greet(): msg = "안녕" # 関数名前空間: {"msg": "안녕"}関数を呼び出すと、新しい名前空間が作成され、関数が終了すると消えます。そのため、関数内で作成した変数は、外部で使用することはできません。
スコープとLEGBルール
スコープ(scope)は、コード内で名前が見える範囲です。Pythonは、変数を検索する際に、LEGBの順序で探索します。
L — Local 関数内部
E — Enclosing 囲んでいる関数(ネストされた関数)
G — Global モジュールの最上位
B — Built-in Python組み込み(print、lenなど)x = "global" # G
def outer(): x = "enclosing" # E def inner(): x = "local" # L print(x) inner()
outer() # "local" — Lで見つかったので、それ以上の探索は行わない最も内側(L)から探し、見つからない場合は外側に移動します。見つかった時点で探索を停止します。
Local — 関数内部の変数
def calculate(): result = 42 # Local変数 temp = result * 2 return temp
calculate()print(result) # NameError: name 'result' is not defined関数内で代入(=)した変数は、自動的にLocal変数になります。関数の外部では存在しません。
Global — モジュールの最上位の変数
counter = 0 # Global
def increment(): print(counter) # ✅ 読み込みは可能(LEGBでGで見つかる) def reset(): counter = 0 # ⚠️ 新しいLocal変数が作成される!Globalのcounterとは無関係
reset()print(counter) # 依然として0(Globalは変更されない)関数内でGlobal変数を読み込むことは自由にできます。ただし、代入すると、Pythonはそれを新しいLocal変数として認識します。
globalキーワード
Global変数を関数内で変更するには、globalキーワードを使用します。
counter = 0
def increment(): global counter counter += 1
increment()increment()print(counter) # 2global counterは、「この関数内のcounterはLocalではなく、Globalのものである」という宣言です。
ただし、globalを乱用すると、値がどこで変更されているかを追跡するのが難しくなります。可能な限り、関数の引数と戻り値でデータをやり取りすることをお勧めします。
Enclosing — ネストされた関数とnonlocal
def outer(): count = 0 def inner(): nonlocal count count += 1 return count print(inner()) # 1 print(inner()) # 2
outer()nonlocalは、「この変数はLocalではなく、すぐ外側の関数(Enclosing)のものである」という宣言です。クロージャを作成する際に使用します。
よくある間違い — UnboundLocalError
x = 10
def broken(): print(x) # ❌ UnboundLocalError! x = 20
broken()エラーが発生します。Pythonは、関数全体を事前に解析し、x = 20がある場合、xをLocal変数として決定します。しかし、print(x)の時点では、まだLocal変数xに値が代入されていないため、エラーが発生します。
解決策:
def fixed_1(): x = 20 # まず代入 print(x)
def fixed_2(): global x # Globalであることを明示 print(x) x = 20Built-in — Python組み込みの名前
print(len([1, 2, 3])) # 3 — lenはBuilt-in
# 誤って組み込みの名前を上書きするとlist = [1, 2, 3] # ⚠️ Built-in listを上書きnew_list = list("abc") # TypeError!list、dict、print、lenなどの名前は、Built-in名前空間にあります。これらの名前を変数として使用すると、Built-inが隠され、元の機能が使用できなくなります。
# 復旧del list # Local/Globalのlistを削除すると、Built-inが再び表示されるクロージャ — Enclosingの実践的な活用
def make_multiplier(factor): def multiply(x): return x * factor return multiply
double = make_multiplier(2)triple = make_multiplier(3)
print(double(5)) # 10print(triple(5)) # 15multiply関数が返された後も、factorを記憶しています。これがクロージャ(closure) — 関数が定義されたスコープの変数を「閉じて保持する」ことです。
def make_counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment
counter = make_counter()print(counter()) # 1print(counter()) # 2print(counter()) # 3クラスを作成しなくても、状態を維持する関数を作成できます。
名前空間のデバッグ
def debug_scope(): x = 10 y = 20 print(locals()) # {'x': 10, 'y': 20}
debug_scope()print(globals().keys()) # グローバル名前空間のすべての名前locals()とglobals()は、それぞれ現在のスコープの名前空間を辞書として返します。変数がどこに属しているかを確認したい場合に便利です。
import builtinsprint(dir(builtins)) # Built-in名前空間のすべての名前重要なまとめ
| キーワード | 意味 |
|---|---|
| (なし) | 代入 → Local、参照 → LEGBの順序で探索 |
global | 「この変数はGlobalのものである」 |
nonlocal | 「この変数はEnclosing関数のものである」 |
探索順序:Local → Enclosing → Global → Built-in
代入規則:関数内で=を使用すると、Localとみなされるモジュールと名前空間
import mathfrom os import path
# importはモジュール名で名前空間を分離print(math.pi) # math名前空間のpiprint(path.exists) # os.pathのexists
# from import *は名前空間を汚染するfrom math import * # ⚠️ すべての名前が現在のGlobalに流れ込むprint(pi) # 動作するが、どこから来たのか不明確import mathは、mathという名前を1つだけGlobalに追加します。from math import *は、数十の名前をGlobalに流し込み、名前の競合のリスクが高まります。
名前空間の競合によって発生するバグは、デバッグが難しい場合があります。「この変数がどこに属しているか」を常に意識することで、このような間違いを避けることができます。