エラーの内容
UnboundLocalError: local variable 'x' referenced before assignment
数ヶ月に一度は必ずハマるエラーです。変数は明らかに存在しているのに、関数の中で読み込もうとするとPythonが怒り出す。混乱するのは、その変数が確かに存在しているという点です――ただし、ローカルスコープには存在しない。Pythonが代入より先にその変数をローカル変数と判断してしまっているのです。
なぜPythonがこのエラーを投げるのか
Pythonは変数がローカルかグローバルかをコンパイル時に決定します。実行時ではありません。関数本体のどこかに特定の名前への代入があると、Pythonはその名前を関数全体においてローカルとマークします――代入より前の行も含めて。
典型的な落とし穴:
count = 0
def increment():
print(count) # <-- ここでUnboundLocalError
count += 1 # Pythonがこの代入を検出 → 'count'をローカルとマーク
increment()
countはモジュールスコープに存在します。しかしそれは関係ありません。count += 1の一行だけで、Pythonは関数内のcountすべてをローカル変数として扱います。そのため、それより前のprint(count)が、まだ代入されていないローカル変数を読もうとしてしまいます。これがエラーの原因です。
修正手順
修正1:モジュールレベルの変数を変更する場合はglobalを使う
変数がモジュールスコープにあり、それを関数内で変更する必要がある場合は、明示的に宣言します:
count = 0
def increment():
global count # Pythonに伝える:'count'はモジュールレベルの変数を指す
print(count) # モジュールレベルのcountを読む → 0
count += 1
increment()
print(count) # 1
注意点として、globalを使うのは外側の変数を本当に変更する必要がある場合に限りましょう。関数内に代入がなく、単に読み込むだけであればglobalは不要です。
修正2:ネストした関数(クロージャ)ではnonlocalを使う
同じ問題が、スコープが異なる形で現れるケースです。変数がモジュールスコープではなく外側の関数に属している場合:
def outer():
count = 0
def inner():
nonlocal count # outer()の'count'を参照する
count += 1
print(count)
inner() # 1を出力
inner() # 2を出力
outer()
nonlocalはPython 3で追加されました。Python 2ではクロージャで外側の名前を再バインドできなかったため、一般的な回避策は値を可変コンテナに包むことでした。例:count = [0]としてcount[0] += 1のように操作します。
修正3:ローカル変数として先に初期化する
より単純な修正で済む場合もあります――外側の変数と名前が衝突していることに気づいていないケースです:
# 壊れている例:代入前に'result'を読もうとしている
def compute(data):
if data:
result = process(data)
return result # dataが偽値のときUnboundLocalError
# 修正済み:デフォルト値を設定する
def compute(data):
result = None
if data:
result = process(data)
return result
修正4:変更操作そのものを避けるようにリファクタリングする
実のところ、これが最もすっきりした解決策になることが多いです。値を引数として渡し、新しい値を返すようにすれば、グローバル変数は一切不要になります:
count = 0
def increment(n):
return n + 1
count = increment(count)
print(count) # 1
純粋関数はテストしやすく、このカテゴリのバグ自体を根本から防ぐことができます。
気づきにくいパターン
条件付き代入
x = 10
def maybe_assign(flag):
if flag:
x = 99 # Pythonが関数全体で'x'をローカルとマーク
print(x) # flagがFalseのときUnboundLocalError
maybe_assign(False)
対処法は2つあります:関数本体の先頭でxを初期化するか、global xを宣言するかです。どちらでも動きますが、初期化する方が一般的にすっきりします。
複合代入演算子(+=、-=など)
count += 1はcount = count + 1の糖衣構文です。左辺の代入によってPythonはcountをローカルに分類します。そして右辺が(まだ代入されていない)ローカル変数を読もうとします。このエラーの最も多い原因がこれです――しっかり頭に焼き付けておきましょう。
try/exceptブロック内
def load():
try:
data = fetch()
except Exception:
pass
return data # fetch()が例外を投げた場合にUnboundLocalError
fetch()が例外を投げると、代入が実行されないためdataが未定義のままになります。修正:tryブロックの前にdata = Noneを追加してください。
修正の確認方法
元々エラーを引き起こした入力で、直接関数を実行して確認します:
python your_script.py
# またはREPLで:
>>> increment()
0
>>> print(count)
1
UnboundLocalErrorが出なくなり、戻り値も正しければ完了です。
条件付き代入のケースでは、両方の分岐をテストしてください――flag=TrueとFlag=Falseの両方です。エラーは片方のパスでしか現れないため、1回のテストが通っても十分ではありません。
クイックチートシート
- 関数内でモジュールレベルの変数を変更する →
global 変数名を追加 - 外側の関数の変数を変更する →
nonlocal 変数名を追加 - if/tryの片方の分岐でしか代入されない変数 → 分岐の前に初期化する
- 読み込むだけで代入しない場合 → キーワード不要。Pythonは外側のスコープを参照できる
- 長期的に最善の修正 → ミュータブルなグローバル変数を避け、値を引数で渡して結果を返す形にする

