Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
プログラミング初心者・入門者にはわかりにくいものの1つがyield文だと思いますが、今回は、そのyield文の応用「yield from」の基本を解説する記事です。
Contents
初歩的なyield文の使い方については、「【初心者にわかりやすく】Pythonのyield文とジェネレータの基本的な考え方を解説」で解説してあるので、そちらをご覧ください。
次にジェネレータ関数によって作られるジェネレータイテレータオブジェクトとはイテレータの一種です。そしてイテレータとはイテラブルオブジェクトの一種です。
このイテレーラやイテラブルといった概念についての説明は「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」で解説してありますのでそちらをご覧ください。
まずPython公式ドキュメントにおいてyield fromの本質について次のように説明がなされています。
yield from iterable
は本質的にはfor item in iterable: yield item
への単なる速記法です
for文を使った書き方の別バージョンということですね。このことさえ理解できていればもうこの時点で説明は不要かもしれません。
次に使い方ですが、
yield from 何らかの式
という形式で使うのが基本となります。
この「何らかの式」ですが、これはそれが評価された結果としてイテラブルオブジェクトにならなければなりません。
上の公式説明で、
yield from iterable
と書かれているのはそういう意味です。
イテラブル(イテラブルオブジェクト)として有名でわかりやすいのはたとえば、リストですね。
リストを使って実際に試してみましょう。
まずはyield fromを使ってみます。
# 会社名リスト
company_list=["Apple", "Google", "Mircrosoft", "Amazon"]
# ジェネレーター作成
def main_gnrtr():
yield from company_list
# ジェネレータイテレータオブジェクトgnrtr_objを作成し代入
gnrtr_obj = main_gnrtr()
# 組み込み関数のnext()関数を使って1回に1度ずつ処理を進める
while 1:
next(gnrtr_obj)
ここで使った「while 1:」という書き方については、「【Pythonの基本文法】while文は例外発生で中止される。そして「while 1:」とは?」をご覧ください。
結果は、
'Apple'
'Google'
'Mircrosoft'
'Amazon'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
StopIteration
となります。
次に上コードをfor文を使って書き直してみます。
# 会社名リスト
company_list=["Apple", "Google", "Mircrosoft", "Amazon"]
# ジェネレーター作成
def main_gnrtr():
for item in company_list:
yield item
# ジェネレータイテレータオブジェクトgnrtr_objを作成し代入
gnrtr_obj = main_gnrtr()
# 組み込み関数のnext()関数を使って1回に1度ずつ処理を進める
while True:
next(gnrtr_obj)
結果は同じです。
上のyield fromを使ったものと下のfor文を使ったものとの違いは、def main_gnrtr()のところだけです。
それを比較したのが次の画像です。
これで公式ドキュメントのいうところの、
yield from iterable
は本質的にはfor item in iterable: yield item
への単なる速記法です
ということが確認できました。
それではちょっとした応用として、formの後に別のジェネレータを使う場合を考えてみましょう。
上述の前提知識のところでも書きましたが、ジェネレータ関数によって作成されるジェネレータイテレータオブジェクトはイテレータの一種で、イテレータはイテラブル(イテラブルオブジェクト)の一種です。よってジェネレータイテレータオブジェクトもイテラブルです。
「fromの後はイテラブル」であれば良いので、「from ジェネレータイテレータオブジェクト」も可能です。
さっそく作ってみましょう。
先程のコードを少し変更します。
company_list=["Apple", "Google", "Mircrosoft", "Amazon"]
# サブジェネレータの作成
def sub_gnrtr():
i=0
while i<4: # company_listのインデックスは0~3までだから
yield company_list[i]
i += 1
# メインジェネレータの作成
def main_gnrtr():
yield from sub_gnrtr()
gnrtr_obj = main_gnrtr()
while 1:
next(gnrtr_obj)
結果は、
'Apple'
'Google'
'Mircrosoft'
'Amazon'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
StopIteration
となります。これは上のコードで
def main_gnrtr():
yield from company_list
このようにした場合と同じ結果を再現できたことになります。
ここで注意したいのが、
def sub_gnrtr():
i=0
while i<4: # company_listのインデックスは0~3までだから
yield company_list[i]
i += 1
この部分ですね。
ここを単純に次のようにしたくなります。
def sub_gnrtr():
yield company_list
しかしこうすると結果は、
['Apple', 'Google', 'Mircrsoft', 'Amazon']
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
StopIteration
これだと1つのリスト[‘Apple’, ‘Google’, ‘Mircrsoft’, ‘Amazon’]が、1回だけ出力されてそこで終わります。これまでの結果とは異なります。これまでのコードは、あくまでnext()を使ってつぎつぎと文字列を出力するという結果でした。
公式ドキュメントの、
yield from iterable
は本質的にはfor item in iterable: yield item
への単なる速記法です
という説明を見ればなおさら「別にどっちのコードでも同じことでは?」とも思えます。
しかし重要なのは、ジェネレータ関数はあくまでジェネレータという型のオブジェクト(ジェネレータイテレータオブジェクト)を作り出すという基本です。
def sub_gnrtr():
yield company_list
このようにすると、まるでリストを作り出しているような気になります。しかし、できあがるのはあくまでジェネレータという型のオブジェクト(ジェネレータイテレータオブジェクト)なのです。リスト型のオブジェクトではありません。
for item in ['Apple', 'Google', 'Mircrsoft', 'Amazon']:
このようにリストを使った場合は、for文はまずリストをいい感じに動かして、リスト(実際はイテレータへと変化したオブジェクト)がその要素を順番に1つずつ返すという処理を行います。そしてその後1つずつ順番にitemに代入して・・・という感じですね。
ではinの後がジェネレータイテレータオブジェクトになっている場合はどうでしょう。
for文はまずジェネレータイテレータオブジェクトをいい感じに動かし、ジェネレータイテレータオブジェクトは1回ずつ実行され、その結果を返します。
def sub_gnrtr():
yield company_list
今回のこのコードの場合はジェネレータオブジェクトが動くと、[‘Apple’, ‘Google’, ‘Mircrsoft’, ‘Amazon’]というリストそのものが返ってきます。実行結果はリストの要素ではなく、リスト全体そのものです。なおこのコードでは処理は1回だけです。2回目はありません(実際はあるが例外が生じて終了)。
それがitemに代入される結果、item=[‘Apple’, ‘Google’, ‘Mircrsoft’, ‘Amazon’]となるため、リスト全体が出力されてしまうわけですね。