クロージャ
入門Python3 4.7.9
C#でも作ったカウンタを作るコードをPythonでも書いてみます。
def counter (i): def inner(): ret = i i += 1 return ret return inner cnt1 = counter(1) cnt2 = counter(10) print(cnt1()) print(cnt1()) print(cnt1()) print(cnt2()) print(cnt2()) print(cnt2())
簡単ですね、さあ実行してみると・・・
$ py main.py Traceback (most recent call last): File "main.py", line 12, in <module> print(cnt1()) File "main.py", line 4, in inner ret = i UnboundLocalError: local variable 'i' referenced before assignment
あれ・・・エラーが出ます。
エラー内容を調べるとどうやら、関数の外で定義された変数はローカル変数であり、関数の中で参照は出来るが変更はできないとのこと。
つまり上記実装でいうとi変数はinner関数の外で定義された変数なのでiを参照することができるけどiを変更することはできないってことなのです。
うーんじゃあどうすればいいのか。
要はiそのものを変更しなければ良いだけなのでiが参照してる何かなら変更しても問題はないということになります。
つまりcounter関数内でiを一旦リストに書き換えちゃえば良いということです。
def counter (i): i = [i] # リスト化する def inner(): ret = i[0] i[0] += 1 # iは書き換えてないからエラーにならない return ret return inner cnt1 = counter(1) cnt2 = counter(10) print(cnt1()) print(cnt1()) print(cnt1()) print(cnt2()) print(cnt2()) print(cnt2())
$ py main.py 1 2 3 10 11 12
inner関数内でi変数そのものは変更しておらず、あくまでもi変数が参照してるリストを書き換えてるだけなのでエラーにならず期待通りに動くというわけです。
しかしせっかくの便利なクロージャなのにこういったちょっとバッドノウハウ的な使い方しかできないのはちょっと残念ですよね・・・
・・・と思いきや、この話はPython2の時のやり方でPython3からはこういったケースの時に値を変更できるようにする構文が追加されています。すごーい。
それがnonlocal文です。
def counter (i): def inner(): nonlocal i # iは変更しますよエラーにしないでね ret = i i += 1 # 変更してもエラーにならない return ret return inner
これで期待通りになりました。良いですねぇ、クロージャは便利なので今後も使っていきます。