이 트랙 통째 — "루프 + 누적" 을 "생성 + 소비" 로 바꾸는 거. 한번 하면 합성 쉽고, 메모리 싸고, 짧은 코드. 잘 합쳐지는 패턴 짧은 list.
패턴 1 — append-then-return 대신 yield
generator 짜야 하는 가장 명확한 신호 — append 로 list 만들고 반환하는 함수, caller 는 한 번 반복. 변환 — 처음 result = [] 떨어뜨리고, result.append(x) 를 yield x 로, return 떨어뜨리고.
패턴 2 — reducer 안의 generator expression
컴프리헨션을 sum, any, all, max, min, set 에 먹이려 할 때 — 대괄호 떨어뜨려. sum(x*x for x in nums) 가 sum([x*x for x in nums]) 보다 메모리 + short-circuit 에서 이김.
패턴 3 — nested 루프 대신 파이프라인
큰 루프가 여러 다른 단계 한다면 — 각각 generator 로 분해. 파이프라인이 위에서 아래로 읽히고 각 layer 독립 테스트.
패턴 4 — 커스텀 코드 전에 itertools
자체 iterator 짜기 전에 itertools 봐. 조합론 삼총사 (product, permutations, combinations) 가 hand-roll nested 루프 거의 다 대체. chain — 이어붙임. islice — 슬라이싱. groupby — 연속 그룹핑.
안티패턴 — lazy 를 eager 로 되돌리기
특정 이유 없이 파이프라인 중간에 list() X. 같은 iterator 두 번 반복하고 두 번째 pass 가 빈 게 왜인지 묻기 X. list 로 materialize 하는 게 더 단순 + 명확할 때 tee 손에 닿기 X.
Pythonic Way: 반복의 기술 = 언제 materialize 하고 언제 안 하는지 아는 것. 디폴트 lazy. *반드시* 필요할 때 — len/인덱싱/슬라이싱/반복 반복 — materialize. "list 만들고 반환" 디폴트는 generator 없는 언어에서 배운 습관 — Python 은 더 잘 할 수 있게 해줘.
Code
패턴 1 — append 대신 yield·python
# 전 — eager
def squares_v1(nums):
result = []
for n in nums:
result.append(n * n)
return result
# 후 — lazy
def squares_v2(nums):
for n in nums:
yield n * n
# `for x in squares(...)` 소비는 동일
# 근데 v2 는 list 절대 안 만들고 stream
for x in squares_v2(range(5)):
print(x)
# 0 1 4 9 16
패턴 2 — reducer 의 generator expression·python
nums = [1, 2, 3, 4, 5]
# 더 좋음 — 중간 list X, 적용 가능 시 short-circuit
print(sum(x*x for x in nums)) # 55
print(any(x > 3 for x in nums)) # True
print(max(x*2 for x in nums)) # 10
print(set(x % 3 for x in nums)) # {0, 1, 2}
패턴 3 — 작고 합성 가능한 단계·python
def even_only(nums):
for n in nums:
if n % 2 == 0:
yield n
def double(nums):
for n in nums:
yield n * 2
def summed(nums):
return sum(nums)
# 위에서 아래로 읽음
result = summed(double(even_only(range(10))))
print(result) # 40 (2+4+6+8) doubled = 40
패턴 4 — itertools 먼저·python
import itertools as it
# 이렇게 짜지 마
result = []
for x in [1, 2, 3]:
for y in ["a", "b"]:
result.append((x, y))
# 이렇게
result = list(it.product([1, 2, 3], ["a", "b"]))
print(result)
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
함수 process_log 가 로그 파일 열고, 루프 안에서 (timestamp, level, message) 튜플 list 를 append, list 반환, caller 는 에러만 필터. 셋으로 리팩터 — (a) lines_of(path) — strip 된 줄 yield 하는 generator. (b) parsed(lines) — 튜플 yield 하는 generator. (c) errors_only(parsed) — level == "ERROR" 인 튜플만 yield 하는 generator. 원래 caller 코드 (for ts, lvl, msg in errors_only(parsed(lines_of(path))):) 가 다른 변경 없어도 되는 거 보여. 테스트는 진짜 파일 대신 작은 인메모리 문자열.
Progress
Progress is local-only — sign in to sync across devices.