파이썬 코딩의 기술 - 32

JinWooHyun·2021년 7월 6일
0

긴 리스트 컴프리헨션보다는 제너레이터 식을 사용하라

리스트 컴프리헨션의 문제점은 입력 시퀀스와 같은 수의 원소가 들어 있는 리스트 인스턴스를 만들어낼 수 있다는 것이다. 이는 입력이 작으면 큰 문제가 되지 않지만, 입력이 커지면 메모리를 상당히 많이 사용하고 그로 인해 프로그램이 중단될 수 있다.

예를 들어 파일을 읽어 각 줄에 들어 있는 문자 수를 반환하고 싶다고 할때, 이를 리스트 컴프리헨션으로 하려면 파일 각 줄의 길이를 메모리에 저장 해야 한다. 파일이 아주 크거나 절대로 끝나지 않는 네트워크 소켓이라면 리스트 컴프리헨션을 사용하는 것이 문제가 될 수 있다.

value = [len(x) for x in open('my_file.txt')]
print(value)

# >>> [100, 58, 15, ...]

이 문제를 해결하기 위해 파이썬은 제너레이터 식(generator expression) 을 제공한다.
제너레이터 식은 리스트 컴프리헨션과 제너레이터를 일반화한 것이다.
제너레이터 식을 실행해도 출력 시퀀스 전체가 실체화되지는 않는다. 그 대신 제너레이터 식에 들어 있는 식으로부터 원소를 하나씩 만들어내는 이터레이터가 생성된다.

it = (len(x) for x in open('my_file.txt'))
print(it)

# >>> <generator object <genexpr> at 0x108993dd0>

제너레이터 식은 이터레이터로 즉시 평가되며, 더 이상 시퀀스 원소 계산이 진행되지 않는다.

반환된 제너레이터에서 다음 값을 가져오면 제너레이터 식에서 다음 값을 얻어올 수 있다.
제너레이터 식을 사용하면 메모리를 모두 소모하는 것을 염려할 필요 없이 원하는 대로 가져와 소비할 수 있다.

print(next(it))
print(next(it))

# >>> 100
# 57

제너레이터 식의 또 다른 특징은 두 제너레이터 식을 합성할 수 있다는 것이다.
다음 코드에서는 앞의 제너레이터 식이 반환한 이터레이터를 다른 제너레이터 식의 입력으로 사용한다.

roots = ((x, x**0.5) for x in it)
print(next(roots))

# >>> (15, 3.87)

이 이터레이터를 전진시킬 때마다 내부의 이터레이터도 전진되면서, 연쇄적으로 루프가 실행돼 조건식을 평가하고 입력과 출력을 서로 주고 받는다. 이 모든 과정이 가능한 메모리를 효율적으로 사용하면서 이뤄진다.

아주 큰 입력 스트림에 대해 여러 기능을 합성해 적용해야 한다면, 제너레이터 식을 선택하라.
다만 제너레이터가 반환하는 이터레이터에는 상태가 있기 때문에 이터레이터를 한 번만 사용해야 한다.

기억해야 할 내용

  • 입력이 크면 메모리를 너무 많이 사용하기 때문에 리스트 컴프리헨션은 문제를 일으킬 수 있다.
  • 제너레이터 식은 이터레이터처럼 한 번에 원소를 하나씩 출력하기 때문에 메모리 문제를 피할 수 있다.
  • 제너레이터 식이 반환한 이터레이터를 다른 제너레이터 식의 하위 식으로 사용함으로써 제너레이터 식을 서로 합성할 수 있다.
  • 서로 연결된 제너레이터 식은 매우 빠르게 실행되며 메모리도 효율적으로 사용한다.
profile
Unicorn Developer

0개의 댓글