背景
Python のデコレータってカッコ良くないですか? 何も見ずにササっと書けたら絶対かっこいいと思うんですよね。 なので基本的な戦略を学んでみたいと思います。
Fluent Python の 第7章 関数デコレータとクロージャ を参考にしながら進めていきます。
基本のイメージ
冒頭で書いた本のほぼパクリなんですが、例えば deco
という名前のデコレータを予め作っておいたとすれば
@deco def target(): print('running target()')
というような使い方をすることになります。
これは以下のように書いた場合と同じで、つまりデコレータを頭に付けた関数を呼び出したときはそのデコレータの戻り値 deco(target)
を参照しています。
def target(): print('running target()') target = deco(target)
デコレータはその関数を置き換えていることの露骨な例として以下のものを紹介します。
>>> def deco(func): >>> def inner(): >>> print('running inner()') >>> return inner
def
の中に def
があって混乱しますが、deco
という関数は内部で inner
という関数を定義していて、それを返しています。よく見ると引数の func
に対しては何もしていませんね。
これを冒頭の例で呼び出すとこうなります。
>>> @deco >>> def target(): >>> print('running target()') >>> target() running inner()
target
の挙動が inner
に置き換わっていますね。
といっても実際に使用される場合は、引数として渡された関数と同じ関数を返すように定義するこが多いです。
また、二つ目の例でこっそり書いていますが、このデコレータ deco
はモジュールが読み込まれた時点で即時実行されています。
def target(): print('running target()') target = deco(target) # 即時実行
デコレータ自体はモジュールのインポート時に実行されますが、中身の関数、ここでは target
ですが、こいつは当然ながら呼び出されたときだけ実行されます。
クロージャ
JavaScript やってるとよく見る言葉ですよね。 無名関数とよく混同されているが別物だよ、という風に書いてあります。
私はクロージャに慣れてない中で無名関数をよく使っていたので別に混同はしていなかったので特に目から鱗という感じはないのですが、そうらしいです。
本書での例で言えばこのようなものが説明されています。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager
これは次のように使います。
>>> avg = make_averager()
まずこの関数を呼び出すと return 文からわかるように averager
という関数オブジェクトを返します。
そこに int 型の変数を渡すと、 new_value
として代入され、それが series
という list に格納され、series
の中身を全て足し上げたものをその個数で除したもの(つまり平均)が返ってきます。
>>> avg(10) # 10/1 10.0 >>> avg(11) # (10 + 11)/2 = 21/2 10.5 >>> avg(12) # (10 + 11 + 12)/3 = 33/3 11.0
この series
という変数は make_averager
関数の直下に定義されているので、avg(10)
のように averager
関数を呼び出しても毎回初期化されることなく、呼び出されるたびに更新されるわけです。
クラスを使えばこのように書けます。
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)
これをこう使います。
>>> avg = Averager() >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
__call__
メソッドはインスタンスを関数のように呼び出せる特殊メソッドです。
つまり avg(10)
は avg.__call__(10)
と同じです 。
話を戻して、もう一度これを見ます。
def make_averager(): series = [] # ここから def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) # ここまで return averager
averager
関数をよく見ると series
という変数はその外部である make_averager
の中で定義されています。
この series
が 自由変数 と呼ばれるものです。そして、この make_averager
の中身で「ここから」「ここまで」と書いた範囲がクロージャと呼ばれるものです。
クロージャはスコープ外の変数 series
にアクセスできています。
ちなみにここで挙げた関数は averager
関数が呼び出される旅に毎回 list の中身を足し上げて平均を計算しているため非効率的です。
make_averager
関数の内部に合計と個数を持たせておけば、呼び出されるたびに増えた分だけ足せば良いので計算量は一定です。
実際のコードはこうなります。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total count += 1 total += new_value return total/count return averager
急に nonlocal count, total
という風に書いたのには意味があります。
実際これを飛ばすと動きません。
なぜなら count += 1
は count = count + 1
のことで、新しく変数を定義しようとしていることになるからです。
一つ前の例では series.append(new_value)
と書いているので series
という自由変数を直接書き換えていますが、ここでは count
という変数( averager
関数内のローカル変数)に代入しようとします。
total
も同じことをしています。
なので、これはローカルな変数ではなくて自由変数のことだよ、と明示的に宣言してやる必要があります。
それが nonlocal
です。
デコレータ
という訳で簡単なデコレータを書いてみます。 書籍では結構ややこしいデコレータを書いてますので、ここでは思い切ってハイパー簡単な例を勝手に作ります。
def deco(func): def inner(*args, **kwargs): print('Start') result = func(*args, **kwargs) print('End') return inner @deco def target(): print('running target()')
こんな感じだとしてみましょう。
deco
は inner
の関数オブジェクトを返します。
inner
のに渡される引数は *args, **kwargs
で、これで関数へ代入されうる引数の最も一般的な形式を網羅(可変長引数)しています。
args
はキーワードなしの引数を束ねたもので、それを *args
とすることでタプルとしてひとまとめに代入します。可変長の引数を押し込めています。
さらに **kwargs
はキーワード引数です。 kwargs
は辞書型の引数を想定しています。 {'name': 'tanaka', 'age': 100}
が func(name='tanaka', age=100)
と代入される感じです。
なので、このデコレータ deco
はどんな形式の関数であっても受け入れることができます。
定義を見ると、 result = func(*args, **kwargs)
のように呼び出し元の関数が実行されていて、その前後で print
文が二つあります。
実行結果は案の定こうなります。
>>> target() Start running target() End
もう少し工夫できそうですが、死ぬほど眠いので今日はこの辺にしておきます。
次は logging モジュールの良さげな使い方を見て、最終的には使い回しの利く logger モジュールを作ろうと思います。