Python 3: Deep Dive (Part 2 - Iterators, Generators): Itertools(セクション8-2/14)
itertools は Python でデータの反復処理を効率的に行うためのモジュールで、filter や chain などの便利な関数群を提供しています。
データのフィルタリング(filter/filterfalse)、無限シーケンスの生成(count/cycle/repeat)、イテレータの結合(chain/tee)など、様々な反復処理パターンに対応できます。
これらの関数は遅延評価(レイジー評価)を採用しており、大規模なデータセットやストリームデータを扱う際にメモリ効率の良いコードを書くことができます。
Python の `itertools` モジュールは、イテレータとイテラブルの機能を拡張するツールの宝庫です。このポストでは、フィルタリング、無限イテレータ、イテレータの連鎖とコピーのメソッドに焦点を当てた高度な反復処理テクニックを探ります。これらのツールは、特に大規模なデータセットやデータストリームを扱う際に、効率的でクリーンな Python コードを書くために不可欠です。
はじめに
イテレータとイテラブルは Python の基本的な概念で、データのコレクションを効率的に走査することを可能にします。組み込みの `iter()` 関数と `itertools` モジュールは、イテレータを操作および生成するための強力なツールを提供します。このポストでは、データのフィルタリング、無限イテレータの作成、イテレータの連鎖とコピーのメソッドを含む、`itertools` を使用した高度な反復処理テクニックを掘り下げていきます。
イテレータの選択とフィルタリング
イテラブルのフィルタリングはプログラミングでよくある作業です。Python は、条件や述語に基づいてイテラブルからアイテムを選択できるいくつかの関数を提供しています。
`filter` 関数
組み込みの `filter()` 関数は、関数が `True` を返すイテラブルの要素からイテレータを構築します。2つの引数を取ります:
述語:与えられた要素に対して `True` または `False` を返す関数
イテラブル:フィルタリングするコレクション
構文:
filter(function, iterable)
述語が `None` の場合、`filter()` は真値のアイテムを返します。
例:
numbers = [1, 10, 2, 10, 3, 10]
# 4未満の数値をフィルタリング
filtered_numbers = filter(lambda x: x < 4, numbers)
print(list(filtered_numbers)) # 出力: [1, 2, 3]
注意: `filter()` は遅延イテレータを返します。つまり、イテラブルをすぐには処理せず、反復処理時に評価します。
ジェネレータ式を使用して同様の結果を得ることもできます:
filtered_numbers = (x for x in numbers if x < 4)
print(list(filtered_numbers)) # 出力: [1, 2, 3]
`itertools` の `filterfalse`
`itertools.filterfalse()` 関数は `filter()` の逆です。述語が `False` となる要素を返します。
構文:
itertools.filterfalse(function, iterable)
例:
from itertools import filterfalse
numbers = [1, 10, 2, 10, 3, 10]
# 4未満でない数値をフィルタリング(つまり、4以上の数値)
filtered_numbers = filterfalse(lambda x: x < 4, numbers)
print(list(filtered_numbers)) # 出力: [10, 10, 10]
`filter()` と同様、`filterfalse()` も遅延イテレータを返します。
`takewhile` と `dropwhile`
`takewhile`
`itertools.takewhile()` 関数は、述語が `True` である限り、イテラブルから要素を返します。一度述語が `False` を返すと、その後の要素が述語を満たす場合でも処理を停止します。
構文:
itertools.takewhile(function, iterable)
例:
from itertools import takewhile
numbers = [1, 3, 5, 2, 1]
# 5未満の数値を取る
result = takewhile(lambda x: x < 5, numbers)
print(list(result)) # 出力: [1, 3]
`dropwhile`
`itertools.dropwhile()` 関数は、述語が `True` である限り、イテラブルから要素を破棄します。一度述語が `False` を返すと、述語をさらにチェックすることなく残りの要素を返します。
構文:
itertools.dropwhile(function, iterable)
例:
from itertools import dropwhile
numbers = [1, 3, 5, 2, 1]
# 5未満の数値を破棄
result = dropwhile(lambda x: x < 5, numbers)
print(list(result)) # 出力: [5, 2, 1]
`compress` 関数
`itertools.compress()` 関数は、セレクタの対応する要素が `True` と評価される場合のみデータ要素をフィルタリングします。
構文:
itertools.compress(data, selectors)
例:
from itertools import compress
data = ['a', 'b', 'c', 'd', 'e']
selectors = [True, False, 1, 0]
result = compress(data, selectors)
print(list(result)) # 出力: ['a', 'c']
この例では:
`'a'` は `True` が真値なので含まれます
`'b'` は `False` が偽値なので除外されます
`'c'` は `1` が真値なので含まれます
`'d'` は `0` が偽値なので除外されます
`'e'` は対応するセレクタがないので除外されます(`False` として扱われます)
無限イテレータ
無限イテレータは、終わりのないデータストリームが必要な場合に便利です。Python の `itertools` モジュールは、無限イテレータを作成するためのいくつかの関数を提供します。
`count`
`itertools.count()` 関数は、連続した数値を無限に生成するイテレータを返します。`range()` 関数と同様に動作しますが、停止値がありません。
構文:
itertools.count(start=0, step=1)
例:
from itertools import islice, count
# 10から始まり、2ずつ増加する無限イテレータを作成
counter = count(10, 2)
# 最初の5要素を取得
first_five = islice(counter, 5)
print(list(first_five)) # 出力: [10, 12, 14, 16, 18]
注意:
`range()` と異なり、`count()` は浮動小数点数や複素数を含む任意の数値型を受け入れることができます
無限ループを避けるため、`islice()` などのツールを使用してイテレータを制限することが重要です
浮動小数点数での `count()` の使用:
counter = count(0.5, 0.5)
first_five = islice(counter, 5)
print(list(first_five)) # 出力: [0.5, 1.0, 1.5, 2.0, 2.5]
`cycle`
`itertools.cycle()` 関数は、イテラブルの要素を無限に繰り返すイテレータを返します。
構文:
itertools.cycle(iterable)
例:
from itertools import islice, cycle
colors = cycle(['red', 'green', 'blue'])
first_ten = islice(colors, 10)
print(list(first_ten))
# 出力: ['red', 'green', 'blue', 'red', 'green', 'blue', 'red', 'green', 'blue', 'red']
重要な注意:
`cycle()` の引数がイテレータの場合、繰り返し処理される際にメモリに保存されます
大きなイテレータや重要なリソースを消費するイテレータには注意が必要です
`repeat`
`itertools.repeat()` 関数は、同じ値を無限に、または指定された回数だけ生成するイテレータを返します。
構文:
itertools.repeat(object[, times])
例(無限の繰り返し):
from itertools import islice, repeat
infinite_spam = repeat('spam')
first_five = islice(infinite_spam, 5)
print(list(first_five)) # 出力: ['spam', 'spam', 'spam', 'spam', 'spam']
例(有限の繰り返し):
limited_spam = repeat('spam', 3)
print(list(limited_spam)) # 出力: ['spam', 'spam', 'spam']
注意事項:
毎回同じオブジェクトが返されます。可変オブジェクトを繰り返す場合、1つへの変更がすべての参照に影響します。
可変オブジェクトの例:
lists = list(repeat([], 3))
lists[0].append(1)
print(lists) # 出力: [[1], [1], [1]]
`lists` の各リストは実際には同じリストオブジェクトです。
イテレータの連鎖とティーイング
イテレータを扱う際、複数のイテラブルを組み合わせたり、イテレータの独立したコピーを作成する必要がある場合があります。`itertools` モジュールはこれらの作業のためのツールを提供します。
`chain`
`itertools.chain()` 関数は、複数のイテラブルを引数として取り、最初のイテラブルが尽きるまでその要素を生成し、次のイテラブルに進み、すべてのイテラブルが尽きるまで続く単一のイテレータを返します。
構文:
itertools.chain(*iterables)
例:
from itertools import chain
a = [1, 2, 3]
b = ['a', 'b', 'c']
c = [True, False]
combined = chain(a, b, c)
print(list(combined)) # 出力: [1, 2, 3, 'a', 'b', 'c', True, False]
単一のイテラブルから連鎖する:
他のイテラブルを含む単一のイテラブルがある場合、アンパック演算子を使用できます:
iterables = [a, b, c]
combined = chain(*iterables)
ただし、アンパックは即時評価され、大きなイテラブルや無限イテラブルには適していません。代わりに `chain.from_iterable()` を使用します。
`chain.from_iterable`
`chain.from_iterable()` メソッドは、イテラブルのイテラブルから単一のチェーンオブジェクトを構築するクラスメソッドです。他のイテラブルを生成する単一のイテラブルがある場合に便利です。
構文:
itertools.chain.from_iterable(iterable)
例:
from itertools import chain
iterables = [a, b, c]
combined = chain.from_iterable(iterables)
print(list(combined)) # 出力: [1, 2, 3, 'a', 'b', 'c', True, False]
このメソッドは遅延評価され、反復処理が始まるまでイテラブルを評価しません。
カスタム実装:
ジェネレータ関数を使用して `chain.from_iterable()` を模倣できます:
def chain_from_iterable(iterable):
for sub_iterable in iterable:
yield from sub_iterable
`tee`
`itertools.tee()` 関数は、単一のイテラブルから複数の独立したイテレータ(デフォルトは2つ)を返します。これにより、データを複数回または並行して反復処理することができます。
構文:
itertools.tee(iterable, n=2)
例:
from itertools import tee
data = [1, 2, 3, 4, 5]
iter1, iter2 = tee(data)
print(list(iter1)) # 出力: [1, 2, 3, 4, 5]
print(list(iter2)) # 出力: [1, 2, 3, 4, 5]
重要な注意:
混乱を避けるため、`tee()` の後は元のイテラブルを使用すべきではありません
`tee()` は、一方のイテレータが他方より先に進む場合にデータをキャッシュする必要があり、追加のメモリを消費する可能性があります
大きなイテラブルでの `tee()` の使用:
大きなイテラブルや無限イテラブルで `tee()` を使用する場合は、メモリ使用量が増加する可能性があるため注意が必要です。
結論
Python の `itertools` モジュールは、高度な反復処理テクニックのための貴重なリソースです。`filterfalse()`、`takewhile()`、`count()`、`cycle()`、`chain()`、`tee()` などの関数を習得することで、複雑な反復パターンを効率的に処理する、より読みやすいコードを書くことができます。データのフィルタリング、無限シーケンスの生成、イテレータの結合など、これらのツールはイテラブルを効果的に操作する能力を拡張します。
さらなる学習:
ハッピーコーディング!