iterator 와 generator 이해하면 itertools 가 표준 라이브러리에서 가장 유용한 모듈 중 하나. iterable 다루는 빠르고 메모리 효율적인 빌딩 블록 모음. 컴프리헨션처럼 list 만드는 게 아니라 — itertools 함수는 lazy iterator 반환. 파이프라인처럼 합성.
무한 삼총사 — count / cycle / repeat
count(start, step) — start, start+step, ... 영원히. cycle(iter) — iter 의 원소 계속 반복. repeat(value, times) — 같은 값, 옵션으로 정해진 횟수. 항상 *멈추는 뭔가* 와 페어 (islice, break, 유한 iterable 과의 zip) — 안 그러면 무한 루프.
chain / zip_longest / islice — 구조적인 것
chain(a, b, c) — a 의 모든 원소, 그 다음 b, 그 다음 c. concat 의 lazy 버전. zip_longest 는 zip 인데 가장 짧은 거에서 안 멈춤 — 채움 값으로 패딩. islice(iter, start, stop, step) 은 어떤 iterable 도 슬라이싱, 무한도 포함.
groupby — 모두를 놀라게 하는 놈
groupby(iter, key) 는 같은 키 공유하는 *연속* 원소를 그룹. 두 가지 알아야 — 오직 *연속* 원소만 그룹 (모든 그룹 같이 묶고 싶으면 정렬 먼저), yield 되는 그룹은 그 자체로 iterator 라 다음 가기 전에 소비해야 돼. 사람들은 SQL GROUP BY 처럼 동작 기대해 — 사실 Unix uniq 에 더 가까움.
tee — iterator 쪼개기
iterator 는 한 번만 소비 가능. tee(iter, n) 가 한 source 에서 n 개의 독립 iterator 만들어 — 내부적으로 필요 시 버퍼링. 아껴 써 — 한 가지가 다른 가지보다 너무 빨리 가면 버퍼 커져. 대부분 tee 손에 닿는 경우는 source 를 list 로 만드는 게 더 단순해.
product / permutations / combinations
조합론 삼총사. product(a, b) — 데카르트 곱, 모든 (x, y) 페어. permutations(iter, r) — 모든 순서 있는 r 길이 배치. combinations(iter, r) — 순서 없는 r 길이 선택. "~ 의 모든 조합" 류 문제의 nested for 루프 통째 대체.
Code
무한 iterator — 멈추는 뭔가와 페어·python
import itertools as it
# count — range 인데 무한
for n in it.count(10, 2):
if n > 20:
break
print(n) # 10 12 14 16 18 20
# cycle — 라운드로빈
colors = it.cycle(["red", "green", "blue"])
for _ in range(7):
print(next(colors))
# red green blue red green blue red
# repeat — 제한 또는 무한
print(list(it.repeat("x", 4))) # ['x', 'x', 'x', 'x']
# 무한 zip + 유한 list
pairs = list(zip(it.count(1), ["a", "b", "c"]))
print(pairs) # [(1, 'a'), (2, 'b'), (3, 'c')]
chain / zip_longest / islice — 구조적·python
import itertools as it
# chain — iterable 이어붙임
result = list(it.chain([1, 2], [3, 4], [5]))
print(result) # [1, 2, 3, 4, 5]
# zip_longest — 가장 짧은 거에서 안 멈춤
result = list(it.zip_longest([1, 2, 3], ["a", "b"], fillvalue="?"))
print(result) # [(1, 'a'), (2, 'b'), (3, '?')]
# islice — *모든* iterable 슬라이싱, 무한 포함
def naturals():
n = 1
while True:
yield n
n += 1
first_5 = list(it.islice(naturals(), 5))
print(first_5) # [1, 2, 3, 4, 5]
groupby — *연속* 만 그룹·python
import itertools as it
items = ["apple", "ant", "banana", "berry", "apricot"]
# 순진한 groupby — 연속 매치만 그룹
for key, group in it.groupby(items, key=lambda x: x[0]):
print(key, list(group))
# a ['apple', 'ant']
# b ['banana', 'berry']
# a ['apricot'] <- 두 번째 'a' 그룹, 합쳐지지 *않음*
# SQL 스타일 그룹핑은 정렬 먼저
items.sort(key=lambda x: x[0])
for key, group in it.groupby(items, key=lambda x: x[0]):
print(key, list(group))
# a ['apple', 'ant', 'apricot']
# b ['banana', 'berry']
product / permutations / combinations·python
import itertools as it
# product — 데카르트 곱 (nested for 루프 대체)
for x, y in it.product([1, 2], ["a", "b"]):
print(x, y)
# 1 a
# 1 b
# 2 a
# 2 b
# permutations — 모든 순서 있는 배치
print(list(it.permutations("abc", 2)))
# [('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
# combinations — 순서 없는 선택
print(list(it.combinations("abc", 2)))
# [('a', 'b'), ('a', 'c'), ('b', 'c')]
# combinations_with_replacement — 재사용 가능한 선택
print(list(it.combinations_with_replacement("ab", 2)))
# [('a', 'a'), ('a', 'b'), ('b', 'b')]
tee — iterator 하나를 여러 개로·python
import itertools as it
source = (x*x for x in range(5))
a, b, c = it.tee(source, 3)
print(list(a)) # [0, 1, 4, 9, 16]
print(list(b)) # [0, 1, 4, 9, 16]
print(list(c)) # [0, 1, 4, 9, 16]
# 주의 — tee() 후 원본 source 사용 X
# tee 버퍼에 들어감
# 대부분 경우 list 로 materialize 하는 게 더 명확
squares = [x*x for x in range(5)]