Python ジェネレーター
Python ジェネレーター
このチュートリアルでは、Python ジェネレーターを使用して反復を簡単に作成する方法、反復子や通常の関数との違い、およびそれを使用する理由について学習します。
ビデオ:Python ジェネレーター
Python のジェネレーター
Python で反復子を作成するには、多くの作業が必要です。 __iter__()
でクラスを実装する必要があります そして __next__()
メソッド、内部状態の追跡、およびレイズ StopIteration
返される値がない場合。
これは長く、直観に反します。このような状況では、ジェネレーターが助けになります。
Python ジェネレーターは、反復子を作成する簡単な方法です。上記のすべての作業は、Python のジェネレーターによって自動的に処理されます。
簡単に言えば、ジェネレーターとは、(一度に 1 つの値を) 反復処理できるオブジェクト (イテレーター) を返す関数です。
Python でジェネレーターを作成する
Python でジェネレーターを作成するのは非常に簡単です。通常の関数を定義するのと同じくらい簡単ですが、yield
return
の代わりにステートメント
関数に少なくとも 1 つの yield
が含まれている場合 ステートメント (他の yield
を含む場合があります) または return
ステートメント)、ジェネレーター関数になります。両方 yield
そして return
関数から何らかの値を返します。
違いは return
ステートメントは関数を完全に終了します yield
ステートメントは、すべての状態を保存して関数を一時停止し、その後、連続した呼び出しでそこから続行します。
ジェネレーター関数と通常関数の違い
ジェネレーター関数と通常の関数の違いは次のとおりです。
- ジェネレーター関数には 1 つ以上の
yield
が含まれています - 呼び出されると、オブジェクト (イテレータ) を返しますが、すぐには実行を開始しません。
__iter__()
のようなメソッド と__next__()
自動的に実装されます。next()
を使用してアイテムを反復処理できます .- 関数が解放されると、関数は一時停止され、制御は呼び出し元に転送されます。
- ローカル変数とその状態は、連続する呼び出し間で記憶されます。
- 最後に、関数が終了すると、
StopIteration
以降の呼び出しで自動的に発生します。
上記のすべてのポイントを説明する例を次に示します。 my_gen()
という名前のジェネレータ関数があります いくつかの yield
で
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
インタプリタでのインタラクティブな実行を以下に示します。これらを Python シェルで実行して、出力を確認してください。
>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()
>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
上記の例で注目すべき興味深い点の 1 つは、変数 n の値が 各呼び出しの間に記憶されます。
通常の関数とは異なり、関数が生成されたときにローカル変数は破棄されません。さらに、ジェネレーター オブジェクトは 1 回だけ反復できます。
プロセスを再開するには、a = my_gen()
などを使用して別のジェネレーター オブジェクトを作成する必要があります。 .
最後に、for ループでジェネレータを直接使用できることに注意してください。
これは for
ループは反復子を取り、next()
を使用して反復します 関数。 StopIteration
になると自動的に終了します 上げられます。 Python で for ループが実際にどのように実装されているかについては、こちらをご覧ください。
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
# Using for loop
for item in my_gen():
print(item)
プログラムを実行すると、出力は次のようになります:
This is printed first 1 This is printed second 2 This is printed at last 3
ループを含む Python ジェネレーター
上記の例はあまり役に立たず、バックグラウンドで何が起こっているかを理解するためだけに調査しました.
通常、ジェネレーター関数は、適切な終了条件を持つループで実装されます。
文字列を反転するジェネレーターの例を見てみましょう。
def rev_str(my_str):
length = len(my_str)
for i in range(length - 1, -1, -1):
yield my_str[i]
# For loop to reverse the string
for char in rev_str("hello"):
print(char)
出力
o l l e h
この例では、range()
を使用しています。 for ループを使用して逆順でインデックスを取得する関数。
注意 :このジェネレーター関数は、文字列だけでなく、リストやタプルなどの他の種類のイテラブルでも機能します。
Python ジェネレータ式
単純なジェネレーターは、ジェネレーター式を使用してオンザフライで簡単に作成できます。ジェネレーターの構築が容易になります。
匿名関数を作成するラムダ関数と同様に、ジェネレータ式は匿名ジェネレータ関数を作成します。
ジェネレータ式の構文は、Python のリスト内包表記に似ています。ただし、角かっこは丸かっこに置き換えられます。
リスト内包表記とジェネレーター式の主な違いは、リスト内包表記ではリスト全体が生成されるのに対し、ジェネレーター式では一度に 1 つの項目が生成されることです。
彼らは怠惰な実行を持っています(要求されたときだけアイテムを生成します)。このため、ジェネレーター式は、同等のリスト内包表記よりもはるかにメモリ効率が高くなります。
# Initialize the list
my_list = [1, 3, 6, 10]
# square each term using list comprehension
list_ = [x**2 for x in my_list]
# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)
print(list_)
print(generator)
出力
[1, 9, 36, 100] <generator object <genexpr> at 0x7f5d4eb4bf50>
上記で、ジェネレーター式が必要な結果をすぐに生成しなかったことがわかります。代わりに、オンデマンドでのみアイテムを生成するジェネレーター オブジェクトを返しました。
ジェネレーターからアイテムを取得する方法は次のとおりです:
# Initialize the list
my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
print(next(a))
print(next(a))
print(next(a))
print(next(a))
next(a)
上記のプログラムを実行すると、次の出力が得られます:
1 9 36 100 Traceback (most recent call last): File "<string>", line 15, in <module> StopIteration
ジェネレータ式は、関数の引数として使用できます。このように使用する場合、丸括弧は省略できます。
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
Python ジェネレーターの使用
ジェネレータが強力な実装である理由はいくつかあります。
1.実装が簡単
ジェネレーターは、対応するイテレーター クラスと比較して、明確かつ簡潔な方法で実装できます。以下は、反復子クラスを使用して 2 の累乗のシーケンスを実装する例です。
class PowTwo:
def __init__(self, max=0):
self.n = 0
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.n > self.max:
raise StopIteration
result = 2 ** self.n
self.n += 1
return result
上記のプログラムは長くてわかりにくいものでした。では、ジェネレーター関数を使用して同じことを行いましょう。
def PowTwoGen(max=0):
n = 0
while n < max:
yield 2 ** n
n += 1
ジェネレーターは自動的に詳細を追跡するため、実装は簡潔でよりクリーンでした。
2.メモリ効率
シーケンスを返す通常の関数は、結果を返す前にメモリ内にシーケンス全体を作成します。シーケンス内の項目数が非常に多い場合、これはやり過ぎです。
このようなシーケンスのジェネレータ実装はメモリに優しく、一度に 1 つのアイテムしか生成しないため、推奨されます。
3.無限の流れを代表する
ジェネレーターは、無限のデータ ストリームを表す優れた媒体です。無限のストリームはメモリに格納できません。ジェネレータは一度に 1 つのアイテムしか生成しないため、データの無限のストリームを表すことができます。
次のジェネレータ関数は、すべての偶数を生成できます (少なくとも理論上)。
def all_even():
n = 0
while True:
yield n
n += 2
4.ジェネレーターのパイプライン
複数のジェネレーターを使用して、一連の操作をパイプライン処理できます。これは、例を使用して最もよく説明されています。
フィボナッチ数列の数値を生成するジェネレータがあるとします。また、数を 2 乗するためのジェネレーターがもう 1 つあります。
フィボナッチ数列の数値の二乗和を求めたい場合は、ジェネレーター関数の出力を一緒にパイプライン処理することにより、次の方法で実行できます。
def fibonacci_numbers(nums):
x, y = 0, 1
for _ in range(nums):
x, y = y, x+y
yield x
def square(nums):
for num in nums:
yield num**2
print(sum(square(fibonacci_numbers(10))))
出力
4895
このパイプライン処理は効率的で読みやすいです (もちろん、はるかにクールです!)。
Python