第9章: さらに先へ:ジェネレータとデコレータ

この章では、より高度でPythonらしいコードを書くための強力な機能、ジェネレータデコレータを学びます。これらの機能を使いこなすことで、メモリ効率の良いデータ処理を実装したり、既存の関数の振る舞いをエレガントに変更したりできるようになります。

イテレータとイテラブル

Pythonのforループは非常にシンプルで強力ですが、その裏側ではイテレーションプロトコルという仕組みが動いています。これを理解することが、ジェネレータを学ぶ上での第一歩です。

  • イテラブル (Iterable): forループで繰り返し処理が可能なオブジェクトのことです。リスト、タプル、辞書、文字列などがこれにあたります。内部に __iter__() メソッドを持つオブジェクトと定義されます。
  • イテレータ (Iterator): 「次の値」を返す __next__() メソッドを持ち、値を一つずつ取り出すためのオブジェクトです。イテレータは一度最後まで進むと、それ以上値を取り出すことはできません。

forループは、まずイテラブルオブジェクトの __iter__() を呼び出してイテレータを取得し、次にそのイテレータの __next__() を繰り返し呼び出して要素を一つずつ取り出しています。

REPLで動きを見てみましょう。iter()関数でイテレータを取得し、next()関数で要素を取り出します。

最後のnext()呼び出しで StopIteration という例外が発生しているのがわかります。forループはこの例外を検知して、ループを自動的に終了してくれます。

ジェネレータ関数とyieldキーワード

イテレータを自作するには、クラスに __iter__()__next__() を実装する必要がありますが、少し手間がかかります。そこで登場するのがジェネレータです。ジェネレータは、イテレータを簡単に作成するための特別な関数です。

ジェネレータ関数は、通常の関数と似ていますが、値を返すのにreturnの代わりにyieldを使います。

  • yieldの働き: yieldは値を返すだけでなく、その時点で関数の実行を一時停止し、関数の状態(ローカル変数など)を保存します。次にnext()が呼ばれると、停止した場所から処理を再開します。

これにより、巨大なデータセットを扱う際に、全てのデータを一度にメモリに読み込む必要がなくなります。必要な時に必要な分だけデータを生成するため、非常にメモリ効率が良いコードが書けます。

フィボナッチ数列を生成するジェネレータの例を見てみましょう。

ジェネレータ式

リスト内包表記に似た構文で、より簡潔にジェネレータを作成する方法がジェネレータ式です。リスト内包表記の []() に変えるだけで作れます。

リスト内包表記はリストオブジェクトを生成するため、要素数が多いとメモリを大量に消費します。一方、ジェネレータ式はジェネレータオブジェクトを返すため、遅延評価(必要になるまで計算しない)が行われ、メモリ使用量を抑えられます。

巨大なファイルの各行を処理する場合など、ジェネレータ式は非常に有効です。

デコレータの概念と基本的な作り方

デコレータは、既存の関数のコードを一切変更せずに、その関数に新しい機能を追加(装飾)するための仕組みです。これは、関数を受け取って、新しい関数を返す高階関数として実装されます。

ログ出力、実行時間の計測、認証チェックなど、複数の関数に共通して適用したい「横断的な関心事」を扱うのに非常に便利です。

基本的な作り方

デコレータの基本的な構造は、関数を入れ子にすることです。

  1. 外側の関数(デコレータ関数)は、装飾したい対象の関数を引数として受け取ります。
  2. 内側の関数(ラッパー関数)で、受け取った関数を呼び出す前後に、追加したい処理を記述します。
  3. 外側の関数は、この内側の関数を返します。

関数の実行前後にメッセージを表示する簡単なデコレータを見てみましょう。

この書き方をより簡単にするための構文が @(アットマーク)、シンタックスシュガーです。

@my_decorator は、say_goodbye = my_decorator(say_goodbye) と同じ意味になります。こちらのほうが直感的で、Pythonのコードで広く使われています。

ジェネレータとデコレータは、最初は少し複雑に感じるかもしれませんが、使いこなせばよりクリーンで効率的なPythonコードを書くための強力な武器となります。ぜひ積極的に活用してみてください。

はい、承知いたしました。先に作成したチュートリアルの末尾に追加する「この章のまとめ」と「練習問題」を作成します。

この章のまとめ

この章では、Pythonプログラミングをさらに高いレベルへ引き上げるための2つの強力な概念を学びました。

  • ジェネレータ: yieldキーワードを使うことで、メモリ効率に優れたイテレータを簡単に作成できることを学びました。ジェネレータ関数やジェネレータ式を使うことで、巨大なデータストリームや無限シーケンスを、必要な分だけ計算しながら扱うことができます。これは、パフォーマンスが重要なアプリケーションにおいて不可欠なテクニックです。

  • デコレータ: @シンタックスを用いることで、既存の関数のソースコードを変更することなく、機能を追加・変更できることを学びました。デコレータは、ロギング、実行時間計測、アクセス制御といった横断的な関心事を分離し、コードの再利用性を高め、DRY (Don't Repeat Yourself) の原則を維持するのに役立ちます。

これらの機能を使いこなすことは、単に高度な文法を覚えるだけでなく、Pythonの設計思想を理解し、より「Pythonらしい(Pythonic)」コードを書くための重要なステップです。

練習問題1: カウントダウンジェネレータ

countdown(start) というジェネレータ関数を作成してください。この関数は、引数で与えられた start の数値から1まで、1ずつ減っていく数値を順番に yield します。例えば countdown(3) は、3, 2, 1 の順に値を生成します。

practice9_1.py
python practice9_1.py

問題2: 実行時間計測デコレータ

関数の実行時間を計測し、"実行時間: X.XXXX秒" のように表示するデコレータ @measure_time を作成してください。このデコレータを、少し時間のかかる処理を行う関数に適用して、動作を確認してみましょう。

ヒント: 時間の計測には time モジュールが使えます。処理の開始前と終了後で time.time() を呼び出し、その差分を計算します。

practice9_2.py
python practice9_2.py