検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
>>
effective_python

【 Effective Python, 2nd Edition 】yield from ステートメントでネストしたジェネレータ ( nested generators, composed generators ) を効率よく処理しよう! 投稿一覧へ戻る

Published 2020年7月1日12:32 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

ジェネレータを利用することでメモリ消費を抑えることができたり、イテラブル可能な独自クラスを簡単に実装できたり、と多くの利点を享受することができます。
今回は、そんなジェネレータがネストしている場合の効率的な実行方法についてです。


さて、インターバルトレーニング用のプログラムを作りたいと思っています。
最初は速いペースで、ちょっと休んで、次はゆっくりとしたペースで、という指示を出せるようにしたいんです。
そこで、次のような2つのジェネレータを用意しました。


def move(period, speed):
"""ある期間(period)におけるトレーニングスピード(speed)を指示します"""
for _ in range(period):
yield speed


def stop(period):
"""ある期間(period)クールダウンします"""
for _ in range(period):
yield 0



実際のトレーニングプログラムを作成するときには、これら2つのジェネレータを組み合わせて一連の流れを組み立てます。
各インターバルセクション毎にそれぞれのジェネレータを実行し、結果的に1つのトレーニングプログラムを作成します。


def training():
"""2つのジェネレータ (move, stop) を組み合わせて実際のトレーニングプログラムを作ります"""
for section in move(4, 5.0):
yield section
for section in stop(3):
yield section
for section in move(2, 3.0):
yield section


def print_section(section):
"""指示を出力します"""
print(f"Section: {section:.1f}")


def run(func):
"""1つのジェネレータ (func -> training) からの指示に合わせてトレーニングしていきましょう"""
for section in func():
print_section(section)


# トレーニング開始
run(training)
# Section: 5.0
# Section: 5.0
# Section: 5.0
# Section: 5.0
# Section: 0.0
# Section: 0.0
# Section: 0.0
# Section: 3.0
# Section: 3.0



このコードの問題点は training() 内で for 文と yield 文が繰り返し記述されていることでしょう。
見た目も「うるさい」ですし、読解性も悪くなっています。
この例ではインターバルセクションが3つだけにもかかわらずこの有様ですから、より複雑なトレーニングメニューを組むとしたら大変なことになりそうです。


このような場合の解決策は yield from 文を使うことです。


def training_composed():
yield from move(4, 5.0)
yield from stop(3)
yield from move(2, 3.0)


run(training_composed)
# Section: 5.0
# Section: 5.0
# Section: 5.0
# Section: 5.0
# Section: 0.0
# Section: 0.0
# Section: 0.0
# Section: 3.0
# Section: 3.0



yield from 文は、ネストされているジェネレータに一旦処理を丸投げして、そのジェネレータが全ての値を出力し終わると元のジェネレータに制御を戻します。
つまり、ネストしているジェネレータを for 文で処理しながらその都度の値を yield 文で返す、という定型処理を自動でやってくれるんです。


for 文の記述がなくなりコードがスッキリした上に、実行速度も速い、というウレシイおまけつきです。


ネストしているジェネレータを for 文を使って処理した場合、yield from 文を使って処理した場合の処理時間を比較してみましょう。


import timeit


# ネストされるジェネレータ
def child():
for i in range(1_000_000):
yield i


# for 文を使用した従来の構文
def slow():
for i in child():
yield i


# yield from 文を使います
def fast():
yield from child()


baseline = timeit.timeit(
stmt="for _ in slow(): pass",
globals=globals(),
number=50
)
print(f"従来の構文: {baseline:.2f}s")
# 従来の構文: 5.92s


comparison = timeit.timeit(
stmt="for _ in fast(): pass",
globals=globals(),
number=50
)
print(f"yield from 文: {comparison:.2f}s")
# yield from 文: 5.08s


reduction = (baseline - comparison) / baseline
print(f"yield from 文の実行速度: {(baseline / comparison):.2%}")
# yield from 文の実行速度: 116.46%



まとめ:

1: ジェネレータがネストされている場合、yield from 文を利用することでネストされているジェネレータのループ処理を記述する必要がなくなります。
2: yield from 文を利用することで処理速度の向上が期待できます。

この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -
【 Effective Python, 2nd Edition 】ジェネレータ ( generator ) に値を注入したいなら、yield from 式と send() 関数の併用よりも、注入する値を提供するイテレータ ( iterator ) を渡しましょう、の巻
【 Effective Python, 2nd Edition 】throw() メソッドを利用したジェネレータ ( generator ) 内部での状態遷移はなるだけ避けましょう。ネストが深くなってコードの読解性が落ちちゃいますよ!
【 Effective Python, 2nd Edition 】ジェネレータの反復動作中に外部から値を注入して出力結果に反映させよう! 代入式の右辺に yield 式があったり、send() メソッドを使ったり!の巻
【 Python + PostgreSQL 】with ステートメントとコネクションプール ( connection pool ) を活用して、効率的で安全なデータベースコネクション、カーソル ( cursor ) の供給、利用ができるようにしよう!(その1)
【 Python + PostgreSQL 】with ステートメントとコネクションプール ( connection pool ) を活用して、効率的で安全なデータベースコネクション、カーソル ( cursor ) の供給、利用ができるようにしよう!(その2) 🔒
【 Effective Python, 2nd Edition 】Queue クラスを利用した producer-consumer パイプライン ( pipelines ) を構築して、マルチスレッドシーケンス処理をエレガントに管理しよう! 並行実行 ( parallelism ) と並列処理 ( concurrency ) もついでにちゃんとイメージしよう!
【 Effective Python, 2nd Edition 】引数として受け取った値を関数内で複数回「消費」する場合には要注意! イテレータ ( iterator ) とコンテナ ( container ) の違いをちゃんと認識しよう!