ジェネレータとイテレータ
イテレータプロトコル
イテレータは __iter__() と __next__() を持つオブジェクトです。
class CountUp:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 1
return value
for n in CountUp(1, 5):
print(n) # 1, 2, 3, 4
ジェネレータ関数
yield を使うと、イテレータを簡潔に書けます。
def count_up(start, end):
current = start
while current < end:
yield current
current += 1
for n in count_up(1, 5):
print(n) # 1, 2, 3, 4
# ジェネレータは1つずつ値を生成する(遅延評価)
gen = count_up(1, 1000000)
print(next(gen)) # 1
print(next(gen)) # 2
# メモリに全要素を保持しない
ジェネレータの実用例
大きなファイルの行ごと処理
def read_large_file(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# メモリ効率が良い
for line in read_large_file('huge_log.txt'):
if 'ERROR' in line:
print(line)
無限シーケンス
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 最初の10個だけ取得
from itertools import islice
fib10 = list(islice(fibonacci(), 10))
print(fib10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
データパイプライン
def read_csv_lines(path):
with open(path) as f:
next(f) # ヘッダースキップ
for line in f:
yield line.strip().split(',')
def filter_active(rows):
for row in rows:
if row[3] == 'active':
yield row
def extract_names(rows):
for row in rows:
yield row[1]
# パイプラインの組み立て(メモリ効率が良い)
lines = read_csv_lines('users.csv')
active = filter_active(lines)
names = extract_names(active)
for name in names:
print(name)
ジェネレータ式
# リスト内包表記(メモリに全要素を保持)
squares_list = [x**2 for x in range(1000000)] # 大量のメモリ
# ジェネレータ式(1つずつ生成)
squares_gen = (x**2 for x in range(1000000)) # メモリ効率が良い
# sum, max, minなどに直接渡せる
total = sum(x**2 for x in range(100))
print(total) # 328350
yield from
def chain(*iterables):
for it in iterables:
yield from it
result = list(chain([1, 2], [3, 4], [5, 6]))
print(result) # [1, 2, 3, 4, 5, 6]
itertools モジュール
from itertools import chain, product, combinations, groupby, count
# chain: 複数のイテラブルを連結
list(chain('ABC', 'DEF')) # ['A','B','C','D','E','F']
# product: 直積
list(product('AB', '12')) # [('A','1'),('A','2'),('B','1'),('B','2')]
# combinations: 組み合わせ
list(combinations('ABCD', 2)) # [('A','B'),('A','C'),...]
# groupby: グループ化
data = [('A', 1), ('A', 2), ('B', 3), ('B', 4)]
for key, group in groupby(data, key=lambda x: x[0]):
print(key, list(group))
まとめ
yieldでジェネレータ関数を作る(遅延評価)- 大きなデータを扱う際はリストよりジェネレータが効率的
- ジェネレータ式
()はリスト内包表記[]のメモリ効率版 itertoolsで高度なイテレータ操作ができる