yield について

Event

ラクス Python Tips LT会

Presented

2021/02/25 nikkie

❓ Question(チャットお願いします🙏)

ふだん yield 使ってますか?

  • ガッツリ💪

  • たまに😃

  • 初耳!👂

お前、誰よ

  • 我が名は にっきー @ftnext / @ftnext

  • Python歴3年。データサイエンティストなう

  • Love anime!!(@ 🎺🎷🔥 🌈 🏔🏕 👩‍🎨🐯🐟)

  • tips: Python界隈では 自己紹介のエイリアス が「お前、誰よ」(ルーツ

お前、誰よ × tips

関わっているPythonコミュニティ

PyCon JP 2021座長です。スタッフ募集中!📣

LT:yield について

Python Tipsと聞いて、浮かんだネタの1つが yield でした

  • yield とは

  • yield よき

  • yield こんなことまで

  • yield 別の使い方

yield について

  • yield とは

  • yield よき

  • yield こんなことまで

  • yield 別の使い方(Appendix)

yield の簡単な例


def easy_generator():
    yield "👩"
    yield "🐯"
    yield "🐟"

用語「ジェネレータ」

  • yield を使った 関数用語集

  • 例:先の関数 easy_generator もジェネレータ

もう一つの用語「ジェネレータイテレータ」

  • ジェネレータ関数の 返り値用語集


>>> g = easy_generator()  # gはジェネレータイテレータ
>>> type(g)
<class 'generator'>

組み込み関数 next で次の要素を取得

  • next は、イテレータ__next__() メソッドを呼び出し、次の要素を取得(ドキュメント


>>> next(g)  # ジェネレータイテレータの次の要素
'👩'

yield で一時停止

next(g) で 2行目の yield で値が返され、一時停止


def easy_generator():
    yield "👩"  # 👈
    yield "🐯"
    yield "🐟"

再開 & 一時停止


>>> next(g)  # 次の要素
'🐯'

def easy_generator():
    yield "👩"
    yield "🐯"  # 👈
    yield "🐟"

再度 再開 & 一時停止


>>> next(g)  # 次の要素
'🐟'

def easy_generator():
    yield "👩"
    yield "🐯"
    yield "🐟"  # 👈

次がない時:StopIteration 例外(ドキュメント


>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

ジェネレータイテレータは for 文で繰り返せる

ジェネレータ イテレータ


>>> for item in easy_generator():
...     print(item)
...
👩
🐯
🐟

tips: StopIteration 例外は繰り返しの仕組みに関係(PyCon JP 2017 トーク

yield について

  • yield とは

  • yield よき

  • yield こんなことまで

  • yield 別の使い方(Appendix)

yield 何がいいの?

リストも for 文で繰り返せる


def return_list():
    return ["👩", "🐯", "🐟"]

>>> for item in return_list():
...     print(item)
...
👩
🐯
🐟

リストの場合と yield の比較(Appendixで実験)

  • リストの場合はすべての要素をメモリに保持する

    • 長くないリストならいいのですが、長くなると・・😢

  • yieldすべてメモリに展開しない 👈 tips!

    • 一時停止により、一度に1つの要素 を処理

    • リストで全要素をメモリに保持するのにかかる時間が yield では発生しない

例:yield でファイル読み込み


def practical_generator(file_path):
    with open(file_path) as fh:
        for row in fh:
            yield row

例:長いファイル

たくさんの行をもつファイル(example.txt)があります


Kumiko
Haduki
Sapphire
Reina
:

例:yield でファイル読み込み


>>> g = practical_generator("example.txt")
>>> for member in g:
...     print(member.rstrip())  # 右側に付く \n を除く
...
Kumiko
Haduki

yield について

  • yield とは

  • yield よき

  • yield こんなことまで

  • yield 別の使い方(Appendix)

別の再開方法

  • next(g)g.__next__())は、一時停止していた yield の後から再開

  • g.send()値を送って、再開 させられる

    • yield は送られた値を受け取れる(value = yield "🐯"

値を受け取るようにジェネレータを変更


def send_example_generator():
    value = "🐯"
    while True:
        value = yield value
        if not value:
            break
        else:
            value = "🐟"

send メソッド


>>> g = send_example_generator()
>>> g.send(None)  # 開始するときはNoneを送る(next(g)でも開始)
'🐯'

def send_example_generator():
    value = "🐯"
    while True:
        value = yield value  # 初期値 🐯 が返った
        if not value:
            break
        else:
            value = "🐟"

send メソッド


>>> g.send(1)
'🐟'

def send_example_generator():
    value = "🐯"
    while True:
        value = yield value  # valueに1が代入された
        if not value:
            break
        else:
            value = "🐟"

send メソッド


>>> g.send("False")
'🐟'

def send_example_generator():
    value = "🐯"
    while True:
        value = yield value  # valueに"False"が代入された
        if not value:  # bool(value)がFalseならジェネレータ実行は終了
            break
        else:
            value = "🐟"

send メソッド


>>> bool([])
False
>>> g.send([])  # ジェネレータ実行を止める
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

まとめ:yield について

  • yield を使った関数= ジェネレータ

  • 一時停止 & (値を送って) 再開

  • ジェネレータの返り値はジェネレータ イテレータ

  • リストを使って繰り返す場合と比べると、全要素をメモリに展開しないため 省メモリ・省時間

このLTで扱ったtips

  • Python界隈では 自己紹介のエイリアス が「お前、誰よ」

  • 第2水曜 みんなのPython勉強会PyCon JP アーカイブ

  • StopIteration 例外は 繰り返しの仕組み に関係

  • コンテキストマネージャ= with と一緒に使えるオブジェクト

ご清聴ありがとうございました

Enjoy development with yield!

References、Appendix が続きます(よろしければどうぞ!)

References 1/2 ジェネレータ関連

References 2/2

Appendix:yield について

  • yield 別の使い方(本編に入り切らなかった話題)

  • yield のtips

  • 大量の行のファイルを扱う実験

番外編:yield について

  • yield とは

  • yield よき

  • yield こんなことまで

  • yield 別の使い方

ジェネレータを with と一緒に使える

  • contextlib.contextmanager デコレータをジェネレータに付ける(ドキュメント

  • tips: コンテキストマネージャ= with と一緒に使える(用語集

コンテキストマネージャになったジェネレータ


@contextlib.contextmanager
def contextmanager_generator():
    # withのブロックに入る前の処理(__enter__)

    yield  # 値を返したときは as で受け取れる

    # withのブロックを抜けた直後の処理(__exit__)

コード例(『ゼロから作るDeep Learning③』より)

with の中でだけ、Config の属性を書き換え(18章


@contextlib.contextmanager
def using_config(name, value):
    old_value = getattr(Config, name)
    setattr(Config, name, value)
    try:
        yield
    finally:
        setattr(Config, name, old_value)

yield のtips

  • yield from <イテレータ>

  • ref: PEP 380 Python 3.3〜

  • ファイル読み込みの例を次で書き変えます

yield でファイル読み込みの例を書き換え


def practical_generator(file_path):
    with open(file_path) as fh:
        # for row in fh:
        #     yield row
        yield from fh

ジェネレータイテレータの繰り返しは1回のみ


>>> g = easy_generator()
>>> for item in g:
...     print(item)
...
👩
🐯
🐟
>>> for item in g:
...     print(item)
...

ジェネレータイテレータを繰り返し使いたい

  • itertools.teeドキュメント

  • 一つの iterable から n 個の独立したイテレータを返します。


>>> import itertools
>>> g = easy_generator()
>>> g1, g2 = itertools.tee(g)

ジェネレータイテレータを繰り返し使いたい


>>> for item in g1:
...     print(item)
...
👩
🐯
🐟
>>> for item in g2:
...     print(item)
...
👩
🐯
🐟

大量の行のファイルを扱う実験:リストと yield

10行を繰り返して行の数が多いファイルを用意


$ wc -l many_names_1b.txt  # 10億行!(6.4GB)
 1000000000 many_names_1b.txt

実験環境

  • MacBook Pro

  • プロセサ 2.3 GHz Intel Core i5、4コア

  • メモリ 16GB

  • Python 3.9.0

リストを使った場合


$ time python compare_speed.py

real        21m47.461s
user        5m50.540s
sys 10m31.005s

yield を使った場合


$ time python compare_speed.py

real        3m42.741s
user        3m40.157s
sys 0m2.286s

time コマンドの結果の見方

考察

  • user を比べると、リストのほうが長い -> 全ての要素を保持する処理の時間と考えられる

  • sys を比べると、大きな差 -> プログラム処理で巨大なリストを扱うためにOSの処理が必要になったと考えている

大量の行のファイルの読み込み、yield を試してみては?

EOF