Pythonのクロージャ理解する
少し複雑なデコレータを実装したくなったけど、クロージャの挙動ちゃんと理解してないなと思いFluent Pythonで復習しました。
使ったのは第7章5節と6節です。
Fluent Python ―Pythonicな思考とコーディング手法
- 作者: Luciano Ramalho,豊沢聡,桑井博之,梶原玲子
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/10/07
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
関数の外部で定義された非グローバルな変数うにどのようにアクセスしているか確認します。
例として挙がっていたのは値が除々に追加されているリストの平均値を計算するようなavg関数です。
まずはクラスを使って実装すると下記のようになります。
class Averager(): def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total/len(self.series) if __name__ == "__main__": avg = Averager() print(avg(10)) print(avg(11)) print(avg(12))
実行結果: 10.0 10.5 11.0
このときavgはAveragerのインスタンス。
次は同様のものを関数型で実装してみる。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager if __name__ == "__main__": avg = make_averager() print(avg(10)) print(avg(11)) print(avg(12))
実行結果: 10.0 10.5 11.0
このときのavgは make_averager()の返り値であるaverager関数。
avg関数はseriesというaveragerの外で定義されている変数を参照している.
次に、totalやlen(series)を毎回計算するのは効率が悪いので、これまでの値の総和や要素数のみを保持するように書き換えます。
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager if __name__ == "__main__": avg = make_averager() print(avg(10)) print(avg(11)) print(avg(12))
するとエラーがでる。
実行結果: Traceback(most recent call last): ... UnboundLocalError: local variable 'count' referenced before assignment
ここでcountとtotalはaveragerの中で再代入されているのでローカル変数扱いとなってしまいます。
参照はOk,再代入はできない。
けど、nonlocal宣言を使うとこのエラーを回避できる。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total count += 1 total += new_value return total / count return averager if __name__ == "__main__": avg = make_averager() print(avg(10)) print(avg(11)) print(avg(12))
実行結果: 10.0 10.5 11.0
クロージャの挙動なんとなく理解できたんで、頑張ってデコレータの実装します。