객체가 들어있는 리스트를 함수가 파라미터로 받을 때 여러번 이터레이션 하는 것이 중요
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)
"""
이터레이터가 결과를 단 한 번만 만들어내므로 이미 StopIteration예외가 발생한 이터레이터나 제너레이터를 다시 하면 결과를 얻을 수 없다
"""
it = read_visits('my_numbers.txt')
percentages = normalize(it)
print(percentages) #[]
해결
이터레이터를 명시적 소진
이터레이터의 전체 내용을 리스트화
담아둔 리스트에 대해 원하는 수만큼 이터레이션 수행
def normalize_copy(numbers):
numbers_copy = list(numbers) #이터레이셔 복사
total = sum(numbers_copy)
result = []
for value in numbers_copy:
percent = 100 * value / total
result.append(percent)
return result
# 잘 동작한다
it = read_visits('my_numbers.txt')
percentages = normalize_copy(it)
print(percentages)
assert sum(oercentages) == 100.0
"""
메모리를 많이 사용한다.
"""
해결책
호출될 때마다 새로운 이터레이터를 반환하는 함수 받기
lambda활용
def normalize_func(get_iter):
total = sum(get_iter()) #새 이터레이터
result = []
for value in get_iter(): #새 이터레이터
percent = 100 * value / total
result.append(percent)
return result
path = 'my_numbers.txt'
percentages = normalize_func(lambda: read_visits(path))
print(percentages)
assert sum(percentages) == 100.0
더 나은 해결책
이터레이터 프로토콜
파이썬의 for루프나 연관식을 컨테이너 타입의 내용
iter(foo)호출
iter 내장함수는 foo.iter라는 특별 메서드 호출
next특별 메서드 정의
class ReadVisits:
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)
visits = ReadVisits(path)
percentages = normalize(visits)
print(percentages)
assert sum(percentages) == 100.0
다른 방법
from collections.abc import Iterator
def normalize_defensive(numbers):
if isinstance(numbers, Iterator): #반복 가능한 이터레이터인지 검사하는 다른 방법
raise TypeError('컨테이너를 제공해야 합니다')
total = sum(numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
visits = ReadVisits(path)
percentages = normalize_defensive(visits)
assert sum(percentages) == 100.0