はじめに
この実験では、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 要素ずつループ処理する。
- 大規模なデータセットの遅延評価を実装する。
- クラス内でカスタムの反復ロジックを実装する。
- 反復子は 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 におけるジェネレータの一般的な使い方は以下の通りです。
- すべての要素を事前に生成するのではなく、必要に応じて要素を生成する。
- 大規模なデータセットの遅延評価を実装する。
- 関数内でカスタムの反復ロジックを実装する。
- ジェネレータは Python における強力なツールであり、効率的でエレガントなコードを書くために使用できます。
反復子とジェネレータの違い
反復子とジェネレータの主な違いは、それらが実装される方法です。
反復子は、2 つのメソッド __iter__ と __next__ を実装するオブジェクトです。__iter__ メソッドは反復子オブジェクト自体を返し、__next__ メソッドは反復子から次の値を返します。
ジェネレータは、値を返すために yield キーワードを使う関数です。ジェネレータ関数が呼び出されると、すぐに関数本体が実行されるわけではありません。代わりに、関数本体を必要に応じて実行するために使えるジェネレータオブジェクトを返します。
以下は、反復子とジェネレータの主な違いのまとめです。
- 反復子は、
__iter__と__next__メソッドを実装するオブジェクトです。リスト、タプル、または文字列などの反復可能オブジェクトから作成されます。 - ジェネレータは、値を返すために
yieldキーワードを使う関数です。ジェネレータ関数を呼び出すことによって作成されます。 - 反復子はクラスを使って実装できますが、ジェネレータは関数を使って実装されます。
- 反復子は 1 回に 1 要素を返しますが、ジェネレータは必要に応じて要素を生成するために使えるジェネレータオブジェクトを返します。
- 反復子は反復可能オブジェクトの要素を 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 におけるジェネレータ式の一般的な使い方は以下の通りです。
- すべての要素を事前に生成するのではなく、必要に応じて要素を生成する。
- 大規模なデータセットの遅延評価を実装する。
- 簡潔で効率的なコードを書く。
ジェネレータ式は 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 つ以上の
for句と要素を生成する式があります。
違い
- リスト内包表記はリストを生成しますが、ジェネレータ式はジェネレータオブジェクトを生成します。
- リスト内包表記はリストのすべての要素を事前に生成しますが、ジェネレータ式は必要に応じて要素を生成します。
- リスト内包表記は、すべての要素をリストに格納するため、より多くのメモリを使用します。一方、ジェネレータ式は必要に応じて要素を生成するため、より少ないメモリを使用します。
- リスト内包表記は通常、すべての要素を事前に生成するため、実行速度が速いです。一方、ジェネレータ式は通常、必要に応じて要素を生成するため、実行速度が遅いです。
全体的に、リスト内包表記とジェネレータ式の両方は、Python で要素のシーケンスを生成するための便利なツールです。リスト内包表記は一般的に高速で、より多くのメモリを使用します。一方、ジェネレータ式は一般的に低速で、より少ないメモリを使用します。どちらを使用するかは、アプリケーションの特定の要件に依存します。
まとめ
この実験では、Python の組み込みの反復子、ジェネレータ、およびジェネレータ式について学びました。これらの構文がどのようにして Python で効率的でエレガントなコードを書くために使用できるかを見ました。また、ジェネレータを使って素数ジェネレータを実装する方法の例も見ました。



