본문 바로가기
C.W.K.
Stream
Lesson 07 of 07 · published

반복을 Python답게 다루는 네 가지 습관

~12 min · recap, idioms, anti-patterns

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

첫째, 모아서 반환하기 전에 yield를 생각해

호출자가 결과를 한 번씩 읽을 수 있다면 빈 리스트에 append한 뒤 반환하는 대신 바로 yield해. 첫 값이 더 빨리 도착하고 큰 입력도 견뎌.

둘째, 리듀서에는 제너레이터 표현식을 넣어

합계·존재 여부·모두 만족 여부처럼 결과 하나로 줄이는 함수는 중간 리스트를 요구하지 않아. 필요한 값만 흘려보내면 돼.

셋째, 단계를 작게 나눠

읽기·파싱·필터링을 따로 만들면 각 단계가 독립적으로 시험되고 다른 조합에도 쓰여. 하나의 거대한 제너레이터보다 이름 붙은 작은 흐름이 읽기 좋아.

넷째, 직접 중첩 반복문을 쓰기 전에 itertools를 봐

이어 붙이기, 구간 고르기, 조합, 묶기 같은 반복 모양은 이미 검증된 표준 도구가 있을 가능성이 커. 다만 도구 이름이 의도를 가리지 않는지도 함께 판단해.

게으른 흐름을 무심코 다시 쌓지 마

뚜렷한 이유 없이 흐름 한가운데서 list()를 호출하면 지금까지의 지연 계산을 한꺼번에 메모리에 올려. 같은 반복자를 두 번 순회하고 두 번째가 빈 것을 버그로 오해하지도 마. 반복 복사가 꼭 필요하더라도 작은 값을 그냥 목록으로 만드는 편이 선명하다면 tee보다 그쪽이 나아.

Python다운 기준: 기본은 게으르게 흘려보내고, 길이·인덱싱·슬라이스·반복 순회처럼 저장이 실제로 필요한 경계에서만 구체적인 모음으로 만들어.

Code

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
리듀서 안의 제너레이터 표현식·python
nums = [1, 2, 3, 4, 5]

# 더 좋음 — 중간 list가 없고, 적용 가능하면 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}
작고 조합 가능한 단계 만들기·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
직접 반복하기 전에 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')]

External links

Exercise

로그 파일을 읽어 튜플 리스트를 만들고 호출자가 ERROR만 거르던 함수를 세 제너레이터로 나눠. lines_of(path)는 정리한 줄, parsed(lines)(timestamp, level, message), errors_only(parsed)는 ERROR만 내주게 해. 인메모리 문자열로 시험하고 호출부가 for ts, lvl, msg in errors_only(parsed(lines_of(path))):처럼 읽히는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.