UnboundLocalError: local variable referenced before assignment の原因と直し方[Python]
外側で定義した変数を関数の中で使おうとしただけなのに、こんなエラーが出ることがある。
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value古いバージョンのPythonだとこう表示される。
UnboundLocalError: local variable 'count' referenced before assignment一見すると「定義したはずなのに」と混乱するが、Pythonのスコープの決め方を知ると理由がはっきりする。
なぜ起きるのか
Pythonは関数を実行する前に、その関数の中身を見て「この名前はローカル変数かどうか」を先に決めてしまう。判定の基準は単純で、関数の中のどこかにその名前への代入があれば、その名前は最初から最後までローカル変数として扱われる。
count = 0
def add(): print(count) # ここで UnboundLocalError count = count + 1print(count)の時点ではまだ代入していないので外側のcountを読みそうに見えるが、関数の後半にcount = ...があるため、Pythonはこの関数内のcountをすべてローカル変数だと判断している。ローカル変数countはまだ値が入っていないので、参照した瞬間にエラーになる。
代入が参照より後ろにあっても関係ないのがポイントになる。位置ではなく、関数全体に代入が存在するかどうかで決まる。
直し方1. 引数と戻り値にする(推奨)
いちばん素直で、あとから読んで混乱しない形になる。
def add(count): print(count) return count + 1
count = 0count = add(count)外の状態を関数が勝手に書き換えないので、テストもしやすくなる。基本的にはこの形にできないか最初に検討したい。
直し方2. global を使う
どうしてもモジュール全体の変数を書き換えたい場合はglobalで宣言する。
count = 0
def add(): global count print(count) count = count + 1ただしこれは、どこから書き換えられるかが追いにくくなるので、多用しないほうがいい。
直し方3. nonlocal を使う(入れ子の関数の場合)
外側が関数の場合はglobalではなくnonlocalになる。
def outer(): count = 0 def inner(): nonlocal count count += 1 inner() return countglobalはモジュールの一番外のスコープを指すので、入れ子関数で使うと目的の変数に届かない。ここは間違えやすい。
つまずきやすいパターン
+= は代入である
読み出しだけのつもりでも、+=は代入なのでローカル判定を引き起こす。
total = 0
def f(): total += 1 # UnboundLocalErrorif の中だけで代入している
条件が成立しなかった場合、代入が実行されないまま参照に到達する。これは関数の外側に同名の変数がなくても起きる。
def f(flag): if flag: msg = "ok" print(msg) # flag が False だと UnboundLocalError先に初期値を入れておけば解決する。
def f(flag): msg = "" if flag: msg = "ok" print(msg)try の中でだけ代入している
例外が起きると代入されないままexceptや後続に進む。
def f(): try: result = risky() except Exception: pass return result # 例外時に UnboundLocalErrorこれも初期化しておくか、except側でも値を入れる。
def f(): result = None try: result = risky() except Exception: pass return resultループが一度も回らなかった
反復対象が空だと、ループ内の変数は作られない。
def f(items): for x in items: last = x return last # items が空だと UnboundLocalError確認のしかた
どの名前がローカル扱いになっているかは、コンパイル結果から確認できる。
def f(): print(count) count = 1
print(f.__code__.co_varnames) # ('count',) と出ればローカル判定されているここに名前が出ていれば、その関数内では外側の変数ではなくローカル変数として扱われている。
📘 こうした細かい挙動をまとめて押さえるなら、独習Python。
まとめ
・Pythonは関数内に代入があれば、その名前を関数全体でローカル変数と判定する。代入が参照より後ろでも同じ
・まずは引数と戻り値で受け渡す形に直せないかを検討する。いちばん安全で読みやすい
・モジュール変数を書き換えるならglobal、外側の関数の変数ならnonlocalを使う。両者は指す先が違う
・+=も代入なので同じエラーを引き起こす
・if、try、forの中でだけ代入している場合、その分岐を通らないと未定義になる。先に初期値を入れておく
・f.__code__.co_varnamesでローカル判定された名前を確認できる
※本記事にはアフィリエイト広告を含みます。