Pythonは、その実用性とシンプルさにより、世界中の多くのソフトウェア開発者の心に触れてきました。
Python は、データをより簡単に扱うための便利な関数やデータ構造を数多く提供しており、その中には itertools として知られる、データを効率的にループ処理するためのツールも含まれています。
このガイドでは、Pythonのitertoolsを使用して、オブジェクトを繰り返し処理する方法を紹介します。
- filter() –
filter()
関数は、フィルタリング基準(関数またはラムダ)と共に、与えられたシーケンスまたはイテラブルを取込みます。そして、シーケンス内のすべての要素をテストして、その要素がフィルタリング基準に適合するかどうかを判断し、基準に適合する要素のみを返します。 - islice() –
islice()
関数により、ユーザはstart
とstop
を指定して反復処理できるようになり、ジェネレータが返されるようになります。 - map() –
map()
関数は、選択された反復処理可能な要素に指定された変換を適用する反復処理可能なマップオブジェクトを生成します。 - zip() –
zip()
関数は 2 つの反復可能オブジェクトを受け取り、ペアとなる要素のタプルを返します。両方のイテラブルの最初の項目がペアになり、両方のイテラブルの2番目の項目がペアになり、以下同様です。
まず、反復処理可能なオブジェクトと反復処理関数を定義し、その後、上記の4つの反復処理関数の例を見ていきます。
Note: Python 3 では、 filter()
, map()
, zip()
は Python 2 の itertools
関数 ifilter()
, imap()
, izip()
と機能的に等しくなっています。
これらはすべてイテレータを返し、インポートを必要としません。
islice()
は Python 3 の組み込み名前空間に移植されていません。
これを使うには、まだ itertools
モジュールをインポートする必要があります。
イテラブルオブジェクトとは?
反復可能なオブジェクトは、ループや反復が可能なデータを保持するコンテナとして定義できます。
Pythonの反復可能オブジェクトにはリスト、セット、タプル、ディクショナリがあります。
一般的に、反復可能なオブジェクトを扱うときは、for
ループのような基本的なツールを使ってループを回します。
私たちはしばしば、言語が持つ反復作業を支援する機能やツールを無視しがちです。
反復処理ツールは、効率的で標準化された関数(Haskellなどの関数型プログラミング言語で見られるような関数)を提供し、他の反復処理関数と統合して反復処理タスクを数行のコードに簡略化します。
filter()関数
filter()` はビルトイン関数で、イテレート可能なアイテムのグループを受け取り、その中の要素が指定したフィルタ条件を満たしているかどうかをテストすることができます。
filter(function, iterable)
filter()はジェネレータ (
filterオブジェクト) を返すので、それを
list()でラップして、単純なリストに戻すことができます。
もし、forと
if` を使ってフィルタリングを行うのであれば、以下のようになります。
# Create a simple list numbered 0 to 10
number_list = [x for x in range(0,10)]
# Will filter for even numbers
even_numbers = []
for number in number_list:
if number%2 == 0:
even_numbers.append(number)
print(even_numbers)
この結果は
[0, 2, 4, 6, 8]
これに対して、filter()
を使って、同じ条件を渡せば、これと同じ結果を得ることができたはずです。
もし条件が満たされて True
が返されたなら、それはフィルタリングされません。
条件が満たされず、 False
が返された場合は、イテラブルの要素がフィルタリングされます。
この条件は、無名関数 lambda
として、またはスタンドアロン関数として提供することができる。
number_list = [x for x in range(0,10)]
filtered_list = list(filter(lambda number: number % 2 == 0, number_list))
print(filtered_list)
ラムダで指定する場合、 number
は現在フィルタリングしているイテラブルの要素である。
各 number
に対して、それが 2 で割り切れるかどうかをチェックし、割り切れる場合は新しい出力に含まれる。
[0, 2, 4, 6, 8]
この関数が True
か False
を返す限り、この関数を単独で抽出することができ、 lambda
の代わりにここで参照するだけです。
number_list = [x for x in range(0,10)]
def is_even(number):
return number%2==0
filtered_list = list(filter(is_even, number_list))
print(filtered_list)
filter()に似た別の関数として、
filterfalse()が
itertoolsに含まれています。
これはfilter()と対になる関数であり、条件を満たさない要素を返す。
この関数をitertoolsからインポートした後、過去のコードを使って
filterfalse()` を適用すれば、リストから奇数だけを取り出すことができます。
from itertools import filterfalse
number_list = [x for x in range(0,10)]
filtered_list = list(filterfalse(lambda number: number % 2 == 0, number_list))
print(filtered_list)
この結果、奇数番号のフィルタリングされたリストができあがります。
[1, 3, 5, 7, 9]
無名関数の代わりに、スタンドアロン関数を使うこともできます。
from itertools import filterfalse
number_list = [x for x in range(0,10)]
def is_even(number):
return number%2==0
filtered_list = list(filterfalse(is_even, number_list))
print(filtered_list)
islice()関数
islice()関数は
itertoolsライブラリの一部で、反復子オブジェクトを受け取って、関数に与えられた
startと
end` 引数で定義された要素からなるセグメントを返します。
itertools.islice(iterable, start, end)
文字列を islice()
してみましょう。
これはジェネレータを返すので、その結果も含めてリストでラップすることにします。
もし start
引数を省略した場合、この関数は必須の end
引数を指定するまでスライスします。
もし両方が指定された場合は、それらの間でスライスしてそのセグメントを返します。
from itertools import islice
old_string = "I need this, but not this"
print(list(islice(old_string, 11)))
ここでは、 old_string
をその先頭から11番目の要素までスライスしています。
['I', ' ', 'n', 'e', 'e', 'd', ' ', 't', 'h', 'i', 's']
しかし、もし start
という引数を与えれば、特定のセグメントをスライスして取り出すことができます。
from itertools import islice
old_string = "I need this, but not this"
print(list(islice(old_string, 7, 11)))
['t', 'h', 'i', 's']
通常、反復記号を扱うときは、リストのような反復記号で終わらせたいものです。
しかし、スライスは文字列に対してもよく行われる操作で、その場合、リストではなく文字列が必要になります。
ありがたいことに、リストの要素を join()
して文字列に戻すのは簡単です。
print(''.join(list(islice(old_string, 0, 11))))
ここでは、各要素を空の文字列に結合しています。
その結果、スライスされたセグメントは文字列に変換されます。
I need this
map()関数
map` 関数はイテレート可能なオブジェクトと、イテレート可能な項目のすべてに変換を適用する関数を受け取る。
map(function, iterable)
map()関数は Python の組み込み関数に含まれているので、何もインポートする必要はありません。
map() は Python 2 の itertools
モジュールにある imap()
と全く同じ機能を提供します。
一般的には、イテラブルの各要素に対して一括で変換を行いたい場合に非常に便利です。
各要素は、その要素の変換されたバージョン、またはその要素によって、あるいはその要素に対して実行された別の操作の結果に対応します。
例えば,整数の各要素を2のべき乗にしたい場合です.
number_list = [x for x in range(0,10)]
numbers_powered = []
for number in number_list:
numbers_powered.append(number**2)
print(numbers_powered)
これは次のようなシーケンスになります。
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
さて、これを map()
で簡単にすることができます。
print(list(map(lambda x: x**2, number_list)))
イテレート可能な number_list
の各要素について、その要素を 2 の累乗に上げて、新しいリストに入れます。
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
もちろん、無名関数の代わりに、他の関数を定義することもできます。
number_list = [x for x in range(0,10)]
def function(number):
print("Performing transformation on number ", number)
return number**2
print('Original list: ', number_list)
mapped_list = list(map(function, number_list))
print('Transformed list: ', mapped_list)
この結果は
Original list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Performing transformation on number 0
Performing transformation on number 1
Performing transformation on number 2
Performing transformation on number 3
Performing transformation on number 4
Performing transformation on number 5
Performing transformation on number 6
Performing transformation on number 7
Performing transformation on number 8
Performing transformation on number 9
Transformed list: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
zip()関数
zip()関数は
0..n個の反復記号を受け取り、それらの反復記号の n 番目の要素を含む
0..n` 個のタプルを生成する。
zip(iterable_1, iterable_2, iterable_3...)
これは Python 3 以降の組み込み関数で、Python 2 で提供された itertools
モジュールの izip()
と同じ機能を提供します。
ここでは、名前のリストとIDのリストを一緒に zip()
してみましょう。
最初の名前は最初のIDと一緒にzipされ、2番目の名前は2番目のIDと一緒にzipされ、といった具合です。
names_list = ['Francis', 'Drake', 'Alexander', 'Robert', 'Elon']
id_list = ['001', '002', '003', '004', '005']
print(list(zip(names_list,id_list)))
という結果になる。
[('Francis', '001'), ('Drake', '002'), ('Alexander', '003'), ('Robert', '004'), ('Elon', '005')]
注意: 例えば、names_list
の要素が 5 つで、id_list
の要素が 10 つというように、これらの反復記号が同じ形状でない場合、最初の 5 つだけがマップされ、id_list
の残りの部分は無視される。
最も長い共通シーケンスがマップされます。
いつものように、これはジェネレータを返すので、 list()
でラップしています。
2つ以上のイテレータブルを指定しても、同じ機能と動作が得られます。
names_list = ['Francis', 'Drake', 'Alexander', 'Robert', 'Elon']
last_name_list = ['Brown', 'Johnson', 'Tiedemann', 'Mann']
id_list = ['001', '002', '003', '004', '005']
zipped_list = list(zip(names_list, last_name_list, id_list))
print(zipped_list)
[('Francis', 'Brown', '001'), ('Drake', 'Johnson', '002'), ('Alexander', 'Tiedemann', '003'), ('Robert', 'Mann', '004')]
names_listは長さが 5 で、他の 2 つのイテレートは長さが 4 なので、
names_list` の最後の要素にはペアがありません。
これは、異なる文脈で登場する関連したアイテムをグループ化するのに最適なツールです。
結論
Pythonには、高レベルのAPIを通じて、エンジニアが簡単かつ効率的にデータを操作できるように、多くの組み込み関数が用意されています。
イテレーションは非常に一般的な操作で、Pythonのイテレーションツールは要素に対して一行で関数的な操作を行うのに非常に便利です。
このガイドでは、 filter()
, map()
, islice()
, zip()
の各関数について見てきました。
islice()は
itertools` モジュールにあり、組み込みの名前空間には存在しませんが、他のシーケンスの部分配列によく使われるタイプの関数で、このガイドで取り上げた他の関数と一緒によく使われますi。