反復子とジェネレータ

PythonPythonBeginner
オンラインで実践に進む

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験では、Python の組み込みの反復子、ジェネレータ、およびジェネレータ式について学びます。これらの構文がどのように使われて Python で効率的でエレガントなコードを書けるか見ていきます。

到達目標

  • 反復子
  • ジェネレータ
  • ジェネレータ式

反復子

反復子は、反復(ループ)可能なオブジェクトです。1 回に 1 要素ずつデータを返すオブジェクトです。Python では、反復子はリスト、タプル、または文字列などの反復可能オブジェクトから作成されます。

新しい Python インタプリタを開きます。

python3

Python で反復子を作成するには、オブジェクトに 2 つのメソッドを実装する必要があります。__iter____next__ です。

__iter__ は反復子オブジェクト自体を返します。__next__ メソッドは反復子から次の値を返します。返す項目がなくなった場合は、StopIteration をスローする必要があります。

次は、数値のリストを反復する単純な反復子の例です。

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        ## len() はリスト内の要素数
        if self.index >= len(self.data):
            raise StopIteration
        result = self.data[self.index]
        self.index += 1
        return result

iterator = MyIterator([1, 2, 3, 4, 5])
for x in iterator:
    print(x)

出力:

1
2
3
4
5

反復子は便利です。なぜなら、反復可能オブジェクトの要素を一度に 1 つずつアクセスできるからです。一度にすべての要素をメモリに読み込むのではなく、これは特にメモリに収まらない大規模なデータセットを扱う際に便利です。

反復子はまた、Python で遅延評価を実装するためにも使用されます。これは、反復子の要素が必要になるまで生成されるだけで、事前にすべての要素を生成するわけではないことを意味します。これは、不要な要素を生成および格納することを回避できるため、より効率的なアプローチになる場合があります。

反復子から 1 回に 1 要素を取得したい場合は、next() 関数を使用できます。この関数は反復子から次の要素を返します。要素がなくなった場合は、StopIteration 例外をスローします。

iterator = MyIterator([1, 2, 3, 4, 5])
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

## StopIteration
print(next(iterator))

出力:

1
2
3
4

## StopIteration

Python における反復子の一般的な使い方は以下の通りです。

  1. 大規模なデータセットの要素を 1 回に 1 要素ずつループ処理する。
  2. 大規模なデータセットの遅延評価を実装する。
  3. クラス内でカスタムの反復ロジックを実装する。
  4. 反復子は Python における強力なツールであり、効率的でエレガントなコードを書くために使用できます。

ジェネレータ

ジェネレータは、関数を使って作成される特殊な種類の反復子です。関数を使って反復子を作成する簡単な方法です。

ジェネレータ関数は通常の関数と同じように定義されますが、値を返すために return キーワードを使う代わりに、yield キーワードを使います。ジェネレータ関数が呼び出されると、すぐに関数本体が実行されるわけではありません。代わりに、関数本体を必要に応じて実行するために使えるジェネレータオブジェクトを返します。

ジェネレータ関数は、その本体のどこにでも yield 文を持つことができます。ジェネレータ関数が呼び出されると、すぐに関数本体が実行されるわけではありません。代わりに、関数本体を必要に応じて実行するために使えるジェネレータオブジェクトを返します。

次は、数値のリストの二乗を生成するジェネレータ関数の例です。

def my_generator(data):
    for x in data:
        yield x**2

for x in my_generator([1, 2, 3, 4, 5]):
    print(x)

出力:

1
4
9
16
25

ジェネレータは便利です。なぜなら、すべての要素を事前に生成するのではなく、必要に応じて要素を生成できるからです。これは、不要な要素を生成および格納することを回避できるため、より効率的なアプローチになる場合があります。

ジェネレータはまた、Python で遅延評価を実装するためにも使用されます。これは、ジェネレータの要素が必要になるまで生成されるだけで、事前にすべての要素を生成するわけではないことを意味します。これは、不要な要素を生成および格納することを回避できるため、より効率的なアプローチになる場合があります。

Python におけるジェネレータの一般的な使い方は以下の通りです。

  1. すべての要素を事前に生成するのではなく、必要に応じて要素を生成する。
  2. 大規模なデータセットの遅延評価を実装する。
  3. 関数内でカスタムの反復ロジックを実装する。
  4. ジェネレータは Python における強力なツールであり、効率的でエレガントなコードを書くために使用できます。

反復子とジェネレータの違い

反復子とジェネレータの主な違いは、それらが実装される方法です。

反復子は、2 つのメソッド __iter____next__ を実装するオブジェクトです。__iter__ メソッドは反復子オブジェクト自体を返し、__next__ メソッドは反復子から次の値を返します。

ジェネレータは、値を返すために yield キーワードを使う関数です。ジェネレータ関数が呼び出されると、すぐに関数本体が実行されるわけではありません。代わりに、関数本体を必要に応じて実行するために使えるジェネレータオブジェクトを返します。

以下は、反復子とジェネレータの主な違いのまとめです。

  1. 反復子は、__iter____next__ メソッドを実装するオブジェクトです。リスト、タプル、または文字列などの反復可能オブジェクトから作成されます。
  2. ジェネレータは、値を返すために yield キーワードを使う関数です。ジェネレータ関数を呼び出すことによって作成されます。
  3. 反復子はクラスを使って実装できますが、ジェネレータは関数を使って実装されます。
  4. 反復子は 1 回に 1 要素を返しますが、ジェネレータは必要に応じて要素を生成するために使えるジェネレータオブジェクトを返します。
  5. 反復子は反復可能オブジェクトの要素を 1 回に 1 要素ずつアクセスするために使用されますが、ジェネレータは必要に応じて要素を生成するために使用されます。

全体的に、反復子とジェネレータの両方は、Python で要素のシーケンスを反復処理するための便利なツールです。これらは、すべての要素を事前に生成するよりも、1 回に 1 要素ずつシーケンスの要素にアクセスまたは生成することを可能にします。これは、より効率的である場合があります。

高度な例:素数ジェネレータ

この例では、素数を生成するジェネレータを作成します。

まず、補助関数 _is_prime を定義しましょう。この関数は、数が素数の場合は True を返し、そうでない場合は False を返します。

def _is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

次に、ジェネレータ関数 prime_numbers を定義しましょう。

def prime_numbers(n):
    for i in range(2, n+1):
        if _is_prime(i):
            yield i

ジェネレータをテストしてみましょう。

for prime in prime_numbers(20):
    print(prime)

出力:

2
3
5
7
11
13
17
19

ジェネレータ式

ジェネレータ式は、リスト内包表記に似ていますが、リストを作成する代わりにジェネレータオブジェクトを返します。

ジェネレータ式は丸括弧 () を使って定義され、1 つ以上の for 句を含むことができます。それは必要に応じて評価され、式の要素を必要に応じて生成するために使えるジェネレータオブジェクトを返します。

次は、数値のリストの二乗を生成するジェネレータ式の例です。

generator = (x**2 for x in [1, 2, 3, 4, 5])
for x in generator:
    print(x)

出力:

1
4
9
16
25

ジェネレータ式は便利です。なぜなら、すべての要素を事前に生成するのではなく、必要に応じて要素を生成できるからです。これは、不要な要素を生成および格納することを回避できるため、より効率的なアプローチになる場合があります。

ジェネレータ式はまた、Python で遅延評価を実装するためにも使用されます。これは、ジェネレータ式の要素が必要になるまで生成されるだけで、事前にすべての要素を生成するわけではないことを意味します。これは、不要な要素を生成および格納することを回避できるため、より効率的なアプローチになる場合があります。

Python におけるジェネレータ式の一般的な使い方は以下の通りです。

  1. すべての要素を事前に生成するのではなく、必要に応じて要素を生成する。
  2. 大規模なデータセットの遅延評価を実装する。
  3. 簡潔で効率的なコードを書く。

ジェネレータ式は Python における強力なツールであり、効率的でエレガントなコードを書くために使用できます。

ジェネレータ式とリスト内包表記

次は、数値のリストの二乗を生成するリスト内包表記とジェネレータ式の例です。

## リスト内包表記
squares = [x**2 for x in [1, 2, 3, 4, 5]]
print(squares)

## ジェネレータ式
squares_generator = (x**2 for x in [1, 2, 3, 4, 5])
for x in squares_generator:
    print(x)

出力:

[1, 4, 9, 16, 25]
1
4
9
16
25

リスト内包表記とジェネレータ式にはいくつかの類似点と違いがあります。

類似点

  1. リスト内包表記とジェネレータ式の両方が、要素のシーケンスを生成するために使用されます。
  2. 両方とも同じ構文を使用し、1 つ以上の for 句と要素を生成する式があります。

違い

  1. リスト内包表記はリストを生成しますが、ジェネレータ式はジェネレータオブジェクトを生成します。
  2. リスト内包表記はリストのすべての要素を事前に生成しますが、ジェネレータ式は必要に応じて要素を生成します。
  3. リスト内包表記は、すべての要素をリストに格納するため、より多くのメモリを使用します。一方、ジェネレータ式は必要に応じて要素を生成するため、より少ないメモリを使用します。
  4. リスト内包表記は通常、すべての要素を事前に生成するため、実行速度が速いです。一方、ジェネレータ式は通常、必要に応じて要素を生成するため、実行速度が遅いです。

全体的に、リスト内包表記とジェネレータ式の両方は、Python で要素のシーケンスを生成するための便利なツールです。リスト内包表記は一般的に高速で、より多くのメモリを使用します。一方、ジェネレータ式は一般的に低速で、より少ないメモリを使用します。どちらを使用するかは、アプリケーションの特定の要件に依存します。

まとめ

この実験では、Python の組み込みの反復子、ジェネレータ、およびジェネレータ式について学びました。これらの構文がどのようにして Python で効率的でエレガントなコードを書くために使用できるかを見ました。また、ジェネレータを使って素数ジェネレータを実装する方法の例も見ました。