파라미터로 객체의 리스트를 받는 함수 예제

def normalize(numbers):
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result

visits = [15, 35, 80]
percentages = normalize(visits)
print(percentages)

>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
def normalize(numbers):
    total = sum(numbers)                    # 이터레이터 한 번 소모
    result = []
    for value in numbers:                   # 이미 순회가 끝난 이터레이터는 순회를 해도 결과가 나오지 않음
        percent = 100 * value / total
        result.append(percent)
    return result
    
def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)

it = read_visits("/tmp/my_numbers.txt")
percentages = normalize(it)
print(percentages)

>>>
[]
it = read_visits("/tmp/my_numbers.txt")
print(list(it))
print(list(it))     # 이미 소진함

>>>
[15, 35, 80]
[]

문제점 해결

def normalize_copy(numbers):
    numbers = list(numbers)     # 이터레이터를 복사
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result

def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)

it = read_visits("/tmp/my_numbers.txt")
percentages = normalize(it)
print(percentages)

>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
def normalize_func(get_iter):
    total = sum(get_iter())         # 새 이터레이터
    result = []
    for value in get_iter():
        percent = 100 * value / total
        result.append(percent)
    return result

percentages = normalize_func(lambda: read_visits(path))     # 제너레이터를 호출하여 매번 새 이터레이터를 생성하는 람다 표현식을 넘겨줌
print(percentages)

>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]

이터레이터 프로토콜

class ReadVisits(object):               # 이 클래스의 구현 방식이 이터레이터 프로토콜
    def __init__(self, data_path):
        self.data_path = data_path
    
    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)

def normalize(numbers):                 # 원래 함수에 수정을 가하지 않고 넘겨도 제대로 동작
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result

path = "/tmp/my_numbers.txt"
visits = ReadVisits(path)
percentages = normalize(visits)
print(percentages)

>>>
[11.538461538461538, 26.923076923076923, 61.53846153846154]
def normalize_defensive(numbers):
    if iter(number) is iter(numbers):       # 이터레이터는 거부하는 조건
        raise TypeError("Must supply a container")
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result

path = "/tmp/my_numbers.txt"
visits = [15, 35, 80]
normalize_defensive(visits)     # 오류 없음
visits = ReadVisits(path)
normalize_defensive(visits)     # 오류 없음
it = iter(visits)
normalize_defensive(it)         # 오류 발생: iterable하더라도 컨테이너가 아니기 때문