Pythonのクロージャについてのメモ
他の言語でクロージャ書いていて、Pythonはどうだったっけと思ったのでメモ。
Effective Pythonの項目15に詳しかった。
クロージャの説明については割愛。
引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。 (https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3)
Pythonのスコープ
まずPythonのスコープについて。
Pythonだと基本として内側の関数から外側の変数を参照できるが、更新はできない。
# 1.これは動くが ... def outer(): ... x = 1 ... def inner(): ... print(x) ... return x ... return inner >>> o = outer() >>> o() 1 1 # 2.これはエラー >>> def outer(): ... x = 1 ... def inner(): ... print(x) ... x = 2 ... return x ... return inner ... >>> o = outer() >>> o() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment # 3.これもエラー >>> def counter(): ... x = 1 ... def inner(): ... x += 1 ... return x ... return inner >>> c = counter() >>> c() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment
1はinnerから見て外側の変数であるouterのxを参照できている。
2と3はinnerがxをローカル変数として宣言している(代入によって別の変数として宣言している)。
そして、それぞれ初期化前に評価しようとしてUnboundLocalErrorが発生している。
3の累算代入文については若干腑に落ちなかったが、
- xへ代入されているのでaddのローカル変数として宣言されている
- 加算前に評価を行うが、その際には初期化されていないので、UnboundLocalErrorとなる
...ということだと思われる(理解が間違っていたらご教示ください!)。
(参考)
通常の代入とは違い、累算代入文は右辺を評価する前に左辺を評価します。たとえば、a[i] += f(x) はまず a[i] を調べ、f(x) を評価して加算を行い、最後に結果を a[i] に割り当てます。 (http://docs.python.jp/3/reference/simple_stmts.html#augmented-assignment-statements)
Python3.xでのクロージャ
では実際にPythonでクロージャをどう書くかというところで、Python3.xではnonlocalを使ってこう書く
# Python3.x def counter(): x = 0 def inc(): nonlocal x x += 1 return x return inc c = counter() c() # >>> 1 c() # >>> 2 c() # >>> 3
nonlocal 文は、列挙された識別子がグローバルを除く一つ外側のスコープで先に束縛された変数を参照するようにします。これは、束縛のデフォルトの動作がまずローカル名前空間を探索するので重要です。この文は、中にあるコードが、グローバル (モジュール) スコープ以外のローカルスコープの外側の変数を再束縛できるようにします。 (http://docs.python.jp/3/reference/simple_stmts.html#nonlocal)
Python2.xでのクロージャ
ちなみにPython2.xでは、リストの要素を使って以下のように書いていたらしい。
# Python2.x def counter(): x = [0] def inc(): x[0] += 1 return x[0] return inc c = counter() c() # >>> 1 c() # >>> 2 c() # >>> 3
参考
Effective Python ―Pythonプログラムを改良する59項目
- 作者: Brett Slatkin,石本敦夫,黒川利明
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/01/23
- メディア: 大型本
- この商品を含むブログ (4件) を見る