Generator: yield and filter

Pythonista Tips 4/N

公開日: 2021-09-02
更新日: 2023-10-12

  Table of Contents

Generator

Def: Generator

  • ジェネレーターオブジェクトとは, Pythonのシーケンスを作成するオブジェクトのこと.
  • ジェネレーターを用いることで, シーケンス全体を作ってメモリに格納しなくても, シーケンスを反復処理することができる
  • ジェネレーターは反復処理のたびに最後に呼び出されたときにどこにいたかを管理し, 次の値を返す

Python 3.xではrangeはジェネレーターの一つですが, Python 2.xではrangeはリストを返す挙動でした. そのため, リスト形式でメモリに収まる範囲内の整数のシーケンスしか扱うことができないというデメリットがありました.

ジェネレーターの例

平方数を返すジェネレーターを以下のように定義してみます.

1
2
3
4
5
def squared_generator(START: int, LIMIT: int):
    num = START
    while num < LIMIT:
        yield num**2
        num += 1

このとき, squared_generatorは以下のようにジェネレーターオブジェクトを返します

1
2
print(squared_generator(1, 5))
>>> <generator object squared_generator at 0x7f4cfc53fd30>

反復処理は以下のようにして実行可能です

1
2
3
4
5
6
7
for x in squared_generator(1, 5):
    print(x)

>>> 1
>>> 4
>>> 9
>>> 16

ジェネレーターはいつも早いのか?

メモリ効率的ではありますが, 実行速度は必ずしも早いとは限りません.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def compute_by_generator(N):
    start_map = time.time()
    res = sum(squared_generator(0, N))
    print(time.time() - start_map, res)

def compute_by_list(N):
    squared_list = []
    for i in range(0, N):
        squared_list.append(i**2)
    start_map = time.time()
    res = sum(squared_list)
    print(time.time() - start_map, res)

N = 10000000
compute_by_list(N)
>>> 0.1008293628692627 333333283333335000000

compute_by_generator(N)
>>> 0.4377624988555908 333333283333335000000

Generatorの値をfilterする

filter関数

Def:

filter関数は第1引数に指定した関数を用いて, 第2引数に指定した反復可能オブジェクトの要素を評価し, その結果が真となる要素だけを反復するイテレータを返す機能を持つ.

1
filter(function, iterable)

なお, functioniterableについて以下のような制約がある

  • 受け取る反復可能オブジェクトは1つだけ
  • functionに指定する関数の引数は1つだけ

例として, 与えられたリストから偶数の要素のみを抽出したい場合は

1
2
3
4
5
6
7
8
9
def is_even(x):
    return x % 2 == 0

result = filter(is_even, [1, 2, 3, 4])
print(result)
>>> <filter object at 0x7f1b70294ac0>

print(list(result))
>>> [2, 4]

Generator objectに対するfilter

0から始まる平方数を返すgeneratorを以下のように定義します

1
2
3
4
5
def squared_generator():
    base = 0
    while True:
        yield(base**2)
        base += 1

このとき, 先頭の3個の平方数を出力したい場合は

1
2
3
4
5
6
for i in range(0, 3):
    print(i, next(A))

>>> 0 0
    1 1
    2 4

1000以上の平方数の中から先頭の3個の平方数を出力したい場合は

1
2
3
4
5
6
7
8
squaered_over_thousand = filter(lambda x: x >= 1000, squared_generator())

for i in range(0, 3):
    print(i, next(squaered_over_thousand))

>>> 0 1024
    1 1089
    2 1156

REMARKS

Generatorの定義にもよりますが, 上記のようにwhile Trueで定義されたGeneratorに対して

1
list(filter(lambda x: x >= 1000, squared_generator()))

を実行してしまうと, すべての要素(実質無限)に対して真偽判定してからlist objectを返そうとしてしまうので, いつまで経っても実行結果が返ってこないという悲惨な結果が起こってしまいます.



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)