はじめに
関数の中で関数の外の変数を操作するようなコードを書いていると、たまに下記のようなエラーが出ます。
UnboundLocalError: local variable '***' referenced before assignment
初歩的ですが、意外とまとまった良い解説がないので、記事にしておきます。
原因
まず、実際にこのエラーが出るコードを示します。
def f(): print(a) a = 10 f()
実際に出るエラーメッセージは以下のようなものです。
Traceback (most recent call last): File "***.py", line 5, in <module> f() File "***.py", line 2, in f print(a) UnboundLocalError: local variable 'a' referenced before assignment
シンプルな例ですが、ローカル変数aが代入の前にprintで参照されているのがおわかりいただけるかと思います。
さて、実は以下のようにしても同じエラーが出ます。
a = 20 def f(): print(a) a = 10 f()
今度はaが定義されているのになぜ? と思うかもしれませんが、変数のスコープについて考察すると理解できます。
スコープ (scope)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
a = 20と書かれている部分のaはグローバル変数、関数内でa = 10と書かれている部分のaは別物です。前者がある位置をグローバルスコープ、後者がある位置を関数のスコープと言ったりします。この違いがあるからこそ、外の名前空間に悪影響を及ぼさないようにしつつ自由な処理を関数で行うことができるわけです。
ローカル変数とグローバル変数の動作の例
a = 20 def printa(): a = 10 print(a) print(a) # 20 printa() # 10 print(a) # 20
もちろん関数内のスコープから外のスコープの変数が見えないという訳ではなく、通常同名のローカル変数がなければ外側のスコープから変数が探索されます。
a = 20 def printa(): print(a) printa() # 20
ただし、同名のローカル変数がなければ、というのが曲者で、ローカル変数があるかどうかは「構文から静的に決定される」ことになります。具体的にいうと、関数の中でその変数に対して代入文があれば、それはローカル変数とみなされます。なお、+=などの累積代入文なども代入文とみなされます。
どのタイミングで代入文が実行されるのかは考慮されません。一度ローカル変数とみなされてしまえば、最初から最後までローカル変数です。
なんとなく動きそうだけど実際は駄目なコード
a = 30 # 関数の中とは無関係 def f(): print(a) # ここで30になったりはしない(この時点でエラー) a = 40 # これがある限りaはローカル変数 print(a) # 40が出たりはしない() f()
繰り返しますが、「関数の中でその変数に対して代入したらローカル変数」「ローカル変数に決まったとしても、その変数が参照されたタイミングで変数が実在している保障はない(むしろない可能性だって十分にある)」ということです。気をつけましょう。
対処法
ローカル変数なんて面倒くさいものは使わなくていい、という場合はglobal宣言を使うことが出来ます。当たり前ですが、こうしてしまうとグローバル変数を直接扱うのと同じことになり、呼び出しで副作用が発生します。
a = 20 def f(): global a print(a) a = 10 print(a) # 20 f() # 20 print(a) # 10
ローカル変数でいいのだが外部の情報を使いたい、という場合は、引数で値を渡す設計にするべきでしょう。
a = 20 def f(a): print(a) a = 10 print(a) print(a) # 20 f(a) # 20 # 10 print(a) # 20
また、この記事でこれまで書いてきた解説とはあまり関係ありませんが、通常のNameErrorと同様のシチュエーションでもUnboundLocalErrorになることがあります。条件分岐が絡んだりすると、起こりがちです。
たとえばこういうコードは通常NameErrorになります。
if False: # 実際にはTrueにもFalseにもなり得る条件。エラーになるのはFalseになったとき a = 10 print(a) # NameError: name 'a' is not defined
関数の中で同様のことをやるとUnboundLocalErrorです。
def f(): if False: a = 10 print(a) # UnboundLocalError: local variable 'a' referenced before assignment f()
この場合の対処法はNameErrorに準じます。if文の上でaに初期値を代入しておくか、else節を設けてaに適切な値を代入するかで解決します。
参考文献
公式のFAQの関連項目がとてもわかりやすいので、こちらにも目を通しておいてください。
なぜ変数に値があるのに UnboundLocalError が出るのですか? | プログラミング FAQ — Python 3.8.0 ドキュメント
Python のローカルとグローバル変数のルールは何ですか? | プログラミング FAQ — Python 3.8.0 ドキュメント