generatorとは、イテレーターという特殊な種類の、generator iteratorと呼ばれるものを返す関数または式です。
Generator-iteratorは遅延評価です。valuesをメモリに保存せず、必要になったときに値を_生成_します。
ジェネレーター関数は他の関数と見た目は同じですが、1つ以上のyield式を含みます。
各yieldはコードの実行を中断し、現在の実行状態(すべてのローカル変数とtry文を含む)を保存します。
ジェネレーターが再開すると、中断したところから状態を引き継ぎます。通常の関数が呼び出しのたびにリセットされるのとは違います。
ジェネレーターは他のループ関数や再帰関数とほとんど同じように作りますが、yield式が必要です。これについては後ほど詳しく見ていきます。
たとえば、与えられた数値のリストから_平方_を返す関数を考えてみましょう。 現在の書き方では、値を返す前にすべての入力を処理しなければなりません。
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
この関数は、次のようにしてジェネレーターに変換できます。
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
これを書くのは、すべての値を_一度に_生成する必要がないときにジェネレーターを使うためです。 _現在処理している_値だけを計算するので、メモリと処理能力を節約できます。
ジェネレーターは、Pythonのほとんどのiterablesの代わりに使えます。
これには、引数としてiterable/iteratorを必要とする_関数_や_オブジェクト_も含まれます。
squares_generator()ジェネレーターを使うには、次のようにします。
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
generator内の値は、next()関数を使っても生成・取得できます。
next()はジェネレーターイテレーターオブジェクトの__next__()メソッドを呼び出し、コードをそのyield式まで「進めて」評価します。するとyield式が値を「生成」、つまり返します。
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
generator-iteratorを完全に消費して返す値がなくなると、StopIterationエラーを送出します。
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
ジェネレーターイテレーターは、イテレーターの特別な部分集合です。
Iteratorsは、_イテラブル_をループするための仕組み、つまりプロトコルです。
ジェネレーターイテレーターと、一般的なPythonのiterablesが返すイテレーターは非常によく似た動作をしますが、いくつか重要な違いがあります。
indexesを_持ちません_。
足し算や引き算で前後の値にアクセスすることはできず、角括弧([])記法やスライスも使えません。len()関数と一緒には使えません。generator-iteratorからすべての値を集めるときは注意してください!yield式yield式はreturn式と非常によく似ています。
return式とは_異なり_、yieldは_特定の時点_で呼び出し元に値を渡し、追加の値は求められるまでその評価と返却を中断します。
yieldが評価されると、それを囲む関数の実行を一時停止し、その時点での_関数の値を返します。
その後、関数は_スコープに残り、__next__()が呼ばれると、再びyieldに到達するまで実行が再開されます。
yield式を関数の外で使うことはできません。
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
ジェネレーターは多くの場面で役立ちます。
大量になる可能性のある値の集まりを扱うとき、それらをすべてメモリに置きたくないことがあります。 ジェネレーターを使えば、大きなデータを少しずつ処理でき、メモリを節約しパフォーマンスを向上させられます。
ジェネレーターは、処理や計算が_複雑_、高コスト、あるいは_無限_である場合にもとても役立ちます。
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
これで、infinite_sequenceオブジェクトに対して__next__()が呼ばれるたびに、直前の数値 + 1を返します。
Conda Airlinesは、プログラミングの世界で最大の航空会社で、1日に10,000便以上を運航しています!
現在、すべての座席を手作業で乗客に割り当てていますが、これを自動化する必要があります。
乗客の座席の割り当てを自動化するソフトウェアを作るよう依頼されました。 このソフトウェアには、メモリ効率と性能が求められます。
Condaは、航空機の座席の文字を生成したいと考えています。
航空機は座席の列でできています。
各列には_4つの座席_があります。
各列の座席には、必ずA、B、C、Dという名前が付いています。
列の最初の座席がA、2番目の座席がB、というようになります。
Dの次は、またAから始まります。
生成する座席の文字の数を表すintを受け取る、generate_seat_letters(<number>)という関数を実装してください。
関数は、座席の文字の_イテラブル_を返すようにします。
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Condaは、航空機の座席を指定された数だけ生成できるシステムを望んでいます。
各航空機の各列には_4つの座席_があります。
列は数字で表され、1から順に増えていきます。
座席は、1A、1B、1C、1D、2A、2B、2C、2D、3A、3B、3C、3D、…のように並びます。
例を示します。
| x | 1 | 2 |
|---|---|---|
| 列 | 5 | 21 |
| 座席の文字 | A | D |
| 結果 | 5A | 21D |
乗客の迷信から、多くの航空会社では、_13列目_の座席を設けていません。 Conda Airlinesもこの慣習に従っているため、_13列目_の座席は_生成しない_ようにしてください。
生成する座席の数を表すintを受け取る、generate_seats(<number>)という関数を実装してください。
関数は、生成した座席の_イテラブル_を返すようにします。
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
座席を生成する関数ができたので、それを使って乗客に座席を割り当てられます。
乗客の名前のlistを受け取る、assign_seats(<passengers>)という関数を実装してください。
関数は、passengerを_キー_、seat_numberを_値_とする_辞書_を返すようにします。
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlinesは、各チケットに固有のコードを持たせたいと考えています。 大手航空会社であるため、非常に多くの便があります。 つまり、同じ座席番号の便が複数存在します。 そこで、_12_文字の文字列で固有のチケットコードを生成するシステムを作ってほしいと考えています。
このコードは、assigned_seatに続けてflight_idを並べたものから始まります。
コードの残りの部分は0sで埋められます。
seat_numbersのlistと、便番号を表すstringを受け取る、generate_codes(<seat_numbers>, <flight_id>)という関数を実装してください。
関数は、ticket_numberを生成するgeneratorを返すようにします。
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'