ソースコードを作成する上で、「どれだけ効率の良い作りになっているか?」ということは求めなければならない要素だと思います。
製品開発などに携わる様になれば、必須でしょう。
今回は、そんな時に役立つ知識「内包表記」「ジェネレータ式」についてまとめます。
基礎知識
まず、本題に行く前に、ぜひ理解しておいてほしい用語を以下にまとめます。
リスト
リストの特徴は、以下になります。
- 複数の値を、1つの変数にまとめて格納することができる
- モジュールを別途 import する必要がない
- 型に縛られずに利用することができる
- 多次元配列に対応している
- 配列と同じような感覚で使用することができる
※リストの使い方に関する詳細は、こちらを確認してみて下さい。
補足として、配列の特徴は、以下になります。リストに比べて、制限が多いことがうかがえます。そのため、どうしても配列の特徴を活かす必要がある場合を除いて、リストで十分対応できるかと思います。
- 複数の値を、1つの変数にまとめて格納することができる
- モジュールを別途 import する必要がある
- 使用できる型は、数値のみで、文字列などには非対応
- 1次元配列のみ対応
- あらかじめサイズ(要素数)を決めて、メモリを確保する必要がある
イテレータ
繰り返しアクセスすることで順番に要素を取り出す(イテレーション)ことが出来る(イテラブル)オブジェクトのこと。
Pythonでは、リスト・配列などが該当します。
ジェネレータ
イテレータなどの繰り返しが発生するような関数の処理を一旦停止させ、途中で得られる結果を返すことができる機能のこと。
ジェネレータの多くは、通常、値を return で返すところを yield で返します。これにより、returnではそれまでの値をまとめて返しますが、yieldの処理の度に返すようになります。
そのため、返すまでに持つデータの量が少なくするため、メモリ容量の節約にもつながります。
ジェネレータイテレータ
ジェネレータ関数から生成されたイテレータのこと。
内包表記
リストを返す処理を、簡易的に表記する表記のこと。
ジェネレータ式
ジェネレータイテレータを返す処理を、簡易的に表記する表記のこと。
ソースコードで見てみよう
ここでは、上記で記載した内容を用いて、段階的にソースコードの一例を、記載してみます。
リストの内容をReturnで出力する
まずは、メモリ消費量や処理速度などの点で、一番効率が悪いソースコードです。
ソースコードは、以下になります。
- list = [‘1’, ‘A’, ‘[3,4,5]’, ‘BCD’]
- print(list)
- def create_list_for_print(input_list):
- new_list = []
- for temp in input_list:
- new_list.append(temp)
- return new_list
- for str_list in create_list_for_print(list):
- print(str_list)
実行結果は、以下です。
- [‘1’, ‘A’, ‘[3,4,5]’, ‘BCD’]
- 1
- A
- [3,4,5]
- BCD
実施している内容は、①で定義した「リスト」をまず②でそのまま出力しています。これが、実行結果の①です。
④の関数では、引数として受けたリストを、⑤~⑧で1つ1つ分けて「new_list」に格納して、呼び出し元に返しています。
実際に④~⑧を呼び出しているのが⑩で、出力しているのが⑪になります。これが、実行結果の③~⑥になります。
リストの内容をyieldで出力する
次に、上記「リストの内容をReturnで出力する」のソースコードに対して、メモリ消費量や処理速度を改良するように改良したソースコードです。「ジェネレータ」を用いた記載となります。
実行結果は、先述と同様です。
- list = [‘1’, ‘A’, ‘[3,4,5]’, ‘BCD’]
- print(list)
- def create_list_for_print_generator(input_list):
- for temp in input_list:
- yield temp
- for str_list in create_list_for_print_generator(list):
- print(str_list)
①で定義した「リスト」を出力しているという点では同じですが、記載している行数が3行分 減っていることが分かるかと思います。
改良前の④~⑨では、新規で「new list」を宣言して格納していましたが、yield を用いたことで「new list」を新規で宣言する手間を省くことができています。これにより、処理が減ったことで処理速度が速くなっています。
また、⑤でループの度に出力されるので、一定のデータの集まりを保持する必要もなくメモリ消費も防ぐことが可能なのです。
内包表記/ジェネレータ式で、より簡潔な記載に!
次に、内包表記です。内包表記では、返り値の明記をすることはしません。より簡単に、より早い処理速度で、for文で回した結果を返すことができます。なお、最終的な出力はリストになります。
リストに対して内包表記をする場合の基本形は、以下のようになります。
[ 式(返り値) for 変数 in イテレータ ]
上記「リストの内容をReturnで出力する」のソースコードに対して、内包表記を実装したソースコードが以下になります。
実行結果は、先述と同様です。
- list = [‘1’, ‘A’, ‘[3,4,5]’, ‘BCD’]
- print(list)
- created_list = [temp for temp in list]
- for str_list in created_list:
- print(str_list)
「リストの内容をReturnで出力する」のソースコードでは、6行(④~⑨)を要していた内容がこちらでは1行(④)で済んでいます。
より簡潔になっていることが分かるかと思います。
なお、ジェネレータ式では、[ ] を ( ) に変更すれば良いです。そのため、ジェネレータ式での表記を実装したソースコードが以下になります。④行目で「赤」で示している箇所です。
- list = [‘1’, ‘A’, ‘[3,4,5]’, ‘BCD’]
- print(list)
- created_list = (temp for temp in list)
- for str_list in created_list:
- print(str_list)
「リストの内容をyieldで出力する」のソースコードでは、3行(④~⑥)を要していた内容が1行(④)で済んでいます。
番外編
内包表記では、基本形が以下になるので、他にも色々な記載ができます。一例を記載していきます。
[ 式(返り値) for 変数 in イテレータ ]
式を計算式にしてみる
以下では、与えられたリストの内容を2倍にして出力するスクリプトです。
コードサンプル(元の表記):
- numlist = [1,2,3]
- def create_list(input_list):
- new_list = []
- for num2 in input_list:
- num2 *= 2
- new_list.append(num2)
- return new_list
- print(create_list(numlist))
コードサンプル(内包表記):
- numlist = [1,2,3]
- out_numlist = [num*2 for num in numlist]
- print(out_numlist)
実行結果:
[2, 4, 6]
条件分岐を加える
以下では、条件分岐(if文)を使用して、奇数のみを2倍にして出力するスクリプトです。
コードサンプル(元の表記):
- numlist = [1,2,3,4,5,6]
- def create_list(input_list):
- new_list = []
- for num2 in input_list:
- if num2 %2 == 1:
- num2 *= 2
- new_list.append(num2)
- return new_list
- print(create_list(numlist))
コードサンプル(内包表記):
- numlist = [1,2,3,4,5,6]
- out_numlist = [num*2 for num in numlist if num%2==1]
- print(out_numlist)
実行結果:
[2, 6, 10]
繰り返し文の2つ利用する
以下では、繰り返し文(for文)のネストを実行しているスクリプトです。
コードサンプル(元の表記):
- multiList = []
- for row in range(1,3):
- for column in range(1,3):
- multiList.append((row, column))
- print(multiList)
コードサンプル(内包表記):
- out_multiList = [(row,column) for row in range(1,3) for column in range(1,3)]
- print(out_multiList)
実行結果:
[(1, 1), (1, 2), (2, 1), (2, 2)]
まとめ
Pythonでソースコードを作成するテクニック上で、「内包表記」「ジェネレータ式」についてまとめてみました。
効率的な構造にするためにも、ぜひ参考にしてみてください。