Python学習メモ:ジェネレータ・デコレータ
ジェネレータ
ジェネレータはリストやタプルのように、for文で使用できるイテラブルなオブジェクト。リストやタプルは要素数に応じてメモリ使用量も増えるが、ジェネレータは次の要素が求められるたびに新たな要素を生成して返すためメモリ使用量を節約できる。
ジェネレータ関数
内部でyield式を使っている関数をジェネレータ関数という。ジェネレータ関数の戻り値は、ジェネレータイテレータと呼ばれるイテレータ。
このイテレータは特殊メソッド__next__()が呼ばれるたびに関数内の処理が次のyield式まで進む。呼び出し元にyield式に渡した値を返すと、そのときの状態を保持したままその行で処理を中断する。再度特殊メソッドが呼ばれると次の行から処理が再開され、関数を抜けると自動でStopIterationが送出される。
def gen_function(n): print(‘start’) while n: print(f’yield: {n}’) yield n n -= 1 gen = gen_function(2) next(gen)
上の処理でnext(gen)が呼ばれるたびにnの値がデクリメントされる。
ジェネレータ式
リストやタプルなどのイテラブルなオブジェクトがある場合は、内包表記を使ってジェネレータを作成できる。リスト内包表記と同じ構文で[]の代わりに()を使う。
x = [1, 2, 3, 4, 5] gen = (i**2 for i in x) list(gen) # [1, 4, 9, 16, 25]
ジェネレータを使用する際の注意点
関数len()には使用できない。また巨大なジェネレータや無限に返すジェネレータをリストやタプルに渡すとメモリを圧迫したり無限ループになったりするので注意。
ジェネレータのユースケース
値を無限に返したいときや大きなデータを扱いたいときに有効。大量のテキストデータや画像ファイルを扱う際に、行単位やファイル単位でデータを取り出す逐次処理をするとパフォーマンス向上が期待できる。
デコレータ
デコレータは関数やクラスの前後に処理を追加できる機能。関数やクラスの前後に@で始まる文字列を記述すると使用できる。関数の引数チェックや実行時間計測などの用途でよく利用される。
デコレータの実体
関数デコレータの実体は、引数に関数を一つ受け取る呼び出し可能オブジェクト。プログラムの実行中には、関数デコレータが戻り値で返した新しい関数が元の関数名に紐づけられる。
処理時間の計測
処理時間を計測するデコレータの例は以下の通り。詳細はPython実践入門参照。
from functools import wraps import time def elapsed_time(f): @wraps(f) def wrapper(*args, **kwargs): start = time.time() v = f(*args, **kwargs) print(f’{f.__name__}: {time.time() - start}”) return v return wrapper
functors.wraps()を使うことで、実際に実行される関数の名前やDocstringをもとの関数のものに置き換えることができる。これをしないと、デコレータ内のラッパーの関数名がラッパーのものになり、デバッグ時などで不便。
デコレータelapsed_timeの使用例は以下の通り。
@elapsed_time def func(n): return sum(i for i in range(n)) # 0〜n-1までの総和を返す print(f’{func(10000)=:,}’)