前提
まず以下のようなコードについて考えます。
>>> def f(): ... print(a) ... >>> a = "hoge" >>> f() hoge
ここでf()の中のprint()でaを参照しています。aはローカルスコープで定義されていないため、外のスコープ(この場合はグローバルスコープ)にあるのだろうとpythonインタプリタは判断します。
そのため、f()を呼ぶとグローバルスコープで定義したaがprint(a)で出てきます。
ここまでは特に疑問はないと思います。次にこれについて考えます(上から続けて実行します)。
>>> def f(): ... print(a) ... a = "fuga" ... print(a) ... >>> f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in f UnboundLocalError: local variable 'a' referenced before assignment
f()の中でaに対する代入を行うと、aはローカル変数とみなされます。位置は関係なく、スコープは定義時に静的に確定します。なので、UnboundLocalErrorというエラーが発生します。
これは有名な話で、公式ドキュメントのFAQにも載っています。pythonプログラマなら知っていないといけないことです。
プログラミング FAQ — Python 3.6.5 ドキュメント
ここまでが前提です。
確認したかったことと結果
こんな関数定義ではどうなるのか。
>>> def f(): ... print(a) ... if False: ... a = "fuga" ... print(a) ...
原則どおりならスコープは静的に確定しますが、なんとなく違う結果になるという期待も抱かせます。
結果。
>>> f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in f UnboundLocalError: local variable 'a' referenced before assignment
変わらないのだった。
まとめ
「とにかく関数の中で代入されていればローカルスコープ」という原則が何よりも優先される、ということを再確認できました。