Pythonのyield文でメモリ使用量の少ないプログラムを作る方法

プログラミング
スポンサーリンク

プログラムはできるだけCPUやメモリ等のリソース消費量の少ない方法による記述をすることで、システムの障害を未然に防ぐことができます。

Pythonのyield文は、メモリの消費量を抑えて実行できる手法の1つです。

本記事では、Pythonのyield文について解説します。

記事内に記載しているプログラムは、Python3.6.8を使って動作確認をしています。

Pythonのyield文でメモリ使用量の少ないプログラムを作る方法

Pythonのyield文について、以下の内容を解説します。

  • yield文とは
  • yield文の基本
  • yield文を使ったファイルの読み込み
  • yield from文

yield文とは

yieldという英単語を辞書で調べると、「生産する」「譲る」「取って代わる」などの意味がある単語となります。

Pythonにおけるyieldを単語の意味に合わせて言うならば、関数の処理を一時的に呼び出し元に譲って、自分は待機しておくという意味になるかと思います。
もう少しプログラム的に説明すると、関数のyield文の場所で処理を一旦停止して戻り値を返し、再度関数が呼ばれると、続きからスタートする、レジューム機能のようなものとなります。

上述の通り、yield文は関数内で使用され、yield文を含む関数をジェネレータ関数と呼びます。
yield文には、関数の戻り値となる値を指定します。

また、関数内にいくつもyield文を入れることができます。
多くの場合はループの処理にて、一旦停止させるような場面に使われることが多いです。

yield文を使うメリットは、大容量のファイルを読み込む場合など、通常の方法では大量のリソースを消費する場面において、一時的に処理を停止することでリソースの消費を抑えることが出来る点にあります。

yield文の基本

yield文の基本的な使い方は以下のようになります。

def func():
    yield "Hello"
    yield "Python"

上記のジェネレータ関数を利用するには、大きく2種類の方法があります。
1つはジェネレータ関数をnextを使って呼び出す方法です。

f1 = func()
s = next(f1)
print(s) // -> Hello
s = next(f1)
print(s) // -> Python

もう1つは、ループ(fon-in)を使って呼び出す方法です。

f2 = func()
for s in f2:
    print(s) // -> Hello -> Python

ループで利用する場合は、yieldの数分だけループします。
nextで呼び出す場合は、yieldの数を理解して呼び出す必要があり、yield文の数を超えて呼び出すと例外が発生します。

また、以下のようにして、ジェネレータ関数のすべての結果をリストに格納することもできます。

f3 = func()
print(list(f3)) // -> ['Hello', 'Python']

yield文を使ったファイルの読み込み

yield文の使い所は、大量のリソース消費を必要とする場面です。
その1つとなる大きなサイズのファイルを読み込む際に、yield文を使って1行ずつ停止しながら処理を行うことで、リソース消費量を1行分に抑えることができます。

def read_line_generator(filepath):
    with open(filepath) as file:
        for line in file:
            yield line

for line in read_line_generator("./large_size_file.txt"):
    print(line)

上記のジェネレータ関数では、yield文で1行分のデータを返しています。
そのジェネレータ関数を呼び出し側がループで呼び出すことで、1行分のデータを取得しながら処理を行うことができます。

yield from文

yield from文により、いくつものジェネレータ関数を、集約した1つのジェネレータ関数とすることができます。

def odd_number_generator():
    for n in range(10):
        if n % 2 == 1:
            yield n

def even_number_generator():
    for n in range(10):
        if n % 2 == 0:
            yield n

def number_generator():
    yield from odd_number_generator()
    yield from even_number_generator()

for n in number_generator():
    print(n)

上記のプログラムでは、奇数を返すジェネレータ関数odd_number_generatorと偶数を返すeven_number_generatorがあり、それらをyield fromによって集約したnumber_generator関数を定義しています。

集約した関数もジェネレータ関数として、ループで使用することができます。
実行結果は以下のようになります。

1
3
5
7
9
0
2
4
6
8

通常のジェネレータ関数は上から順に実行されていくので、集約されても順番通りに実行されます。
ジェネレータ関数からリストを作成しても、順番は同じになります。

print(list(number_generator())) // -> [1, 3, 5, 7, 9, 0, 2, 4, 6, 8]

まとめ

Pythonのyield文についてまとめると、以下となります。

  • yieldとは、関数の処理を一時的に呼び出し元に譲って(戻して)、自分は待機しておくレジューム機能のようなものである。
  • yield文を使うことで、メモリを大量に使うループなどにおいて、リソース消費量を抑えることができる。
  • yield from文を使うことで、ジェネレータ関数を集約することができる。

リソースの消費量をできるだけ小さくするのは、プログラマの腕の見せどころです。
大量のデータを扱う際は、yield文を使ってみてはいかがでしょうか。

 

今回はPythonのyield文について解説しました。

以上、参考になれば幸いです。

コメント

タイトルとURLをコピーしました