이터레이터와 제너레이터
1. 이터레이터(Iterator)
이터레이터란, 값을 차례대로 꺼낼 수 있는 객체를 의미합니다. 파이썬에서 이터레이터 객체를 생성하기 위해 __iter__
와 __next__
두 가지 매직 메서드를 구현해야 합니다. 여기에 대해 상세하게 살펴보겠습니다.
1.1 이터레이터의 기본 구조
이터레이터 클래스를 생성할 때는 반드시 __iter__
와 __next__
매직 메서드를 정의해야 합니다.
__iter__
매직 메서드는 이터레이터 객체 자신을 반환합니다. 이는 for
루프 등이 이터레이터를 초기화할 수 있게 합니다.
__next__
매직 메서드는 순회를 할 때 값을 하나씩 반환합니다. 더 이상 반환할 값이 없을 때는 StopIteration
예외를 발생시켜 순회가 끝났음을 알립니다.
아래는 간단한 이터레이터 예제입니다.
위 코드는 Licat
클래스를 이터레이터로 만들어 zip
에서 사용하고 있습니다. 만약 for
루프에서 사용하면 Licat
이라는 문자열을 반복하는 무한 반복이 될 것입니다. 동작 원리를 좀 더 상세하게 살펴보겠습니다.
위 예제에서 MyIterator
클래스는 __iter__
와 __next__
메서드를 구현하여 이터레이터를 생성합니다. 이터레이터를 사용할 때에는 for
반복문을 사용하면 됩니다.
for문을 만나면 __iter__
를 가장 먼저 실행합니다. 그리고 그 다음부터는 __next__
를 호출하며 다음 값을 꺼냅니다.
my_iterator 객체는 다시 순회할 수 없는 구조로 코딩이 되어 있습니다. self.current_value가 다음 순회에서도 5가 되어 있기 때문입니다.
💡 위 코드는 https://pythontutor.com/python-debugger.html#mode=edit 서비스를 통해 시각화해서 단계별로 실행해보면서 이해하시길 권해드립니다.
1.2 이터레이터 재사용
MyIterator
클래스로 생성된 인스턴스 my_iterator
는 한 번 순회하면 더 이상 사용할 수 없습니다. 이를 재사용 가능하게 만들기 위해서는 __iter__
메서드에서 self.current_value
변수를 초기화해야 합니다. 이로써 my_iterator
객체는 여러 번 순회할 수 있게 되었습니다.
1.3 이터레이터와 언패킹
파이썬의 이터레이터는 값의 언패킹이 가능합니다. 즉, 이터레이터에서 반환하는 값들을 변수 여러 개에 한 번에 할당할 수 있습니다. 이러한 이터레이터는 언패킹이 가능합니다. 언패킹은 __next__
로 반환된 값을 순서대로 받게 됩니다.
2. 제너레이터(Generator)
제너레이터는 이터레이터를 생성해주는 함수로, yield
키워드를 사용하여 만듭니다. 제너레이터는 함수 실행 중간에 값을 반환할 수 있고, 이후에 함수의 실행을 계속할 수 있는 특징을 가지고 있습니다. 이를 통해 메모리를 효율적으로 관리하며, 코드의 가독성을 향상시킬 수 있습니다.
2.1 기본 제너레이터
이 예제에서, my_generator
함수는 x에서 할당 받은 값을 yield
로 i
값으로 넘겨주고 다시 호출될 때까지 대기하게 됩니다. 이를 통해 넘겨주는 값이 없을 때까지 순회하며 출력할 수 있습니다.
이 코드는 아래와 같이 만들 수 있습니다.
출력 결과는 둘 다 동일합니다.
2.2 무한 제너레이터
이 예제에서, infinite_generator
함수는 무한 루프를 통해 무한한 시퀀스를 생성하는 제너레이터를 만듭니다. for
루프를 사용하여 이 제너레이터를 순회하면서 값을 출력할 수 있습니다. 무한 루프를 사용함에 따라, 조건을 만족하는 시점에서 break
문을 사용하여 반복을 중단해야 합니다.
위 예제에서 infinite_generator
함수는 while
문을 사용하여 무한반복하며, yield
키워드를 사용하여 제너레이터를 생성합니다. 이제 my_iterator
객체를 for
반복문으로 순회하면서 값이 출력됩니다. 이터레이터를 무한히 반복시키기 때문에, break
키워드를 사용하여 10번째 이후의 값은 출력하지 않습니다.
2.3 제너레이터와 빌트인 함수의 조합
제너레이터는 zip
, map
, filter
와 같은 파이썬의 빌트인 함수와 함께 사용하여 코드를 더 간결하고 효율적으로 만들 수 있습니다. 제너레이터를 활용하면, 빅 데이터를 처리하거나, 파일을 읽어올 때 메모리를 절약하면서도 풍부한 표현력을 유지할 수 있습니다.
제너레이터와 이터레이터는 파이썬에서 유연한 코드를 작성할 수 있는 강력한 도구로, 여러분의 코드를 더욱 효율적이고 가독성 좋게 만들어 줍니다.
3.1 제너레이터 컴프리헨션
제너레이터 컴프리헨션은 리스트 컴프리헨션과 매우 유사한 구조를 가지면서도, 생성 시점에 모든 요소를 메모리에 로드하지 않는다는 점에서 차이가 있습니다. 이 특징은 큰 데이터셋을 처리할 때 매우 유용하며, 메모리 사용량을 줄일 수 있습니다.
3.1.1 메모리 효율성
리스트 컴프리헨션은 리스트를 생성하는 시점에 모든 요소를 메모리에 할당합니다. 반면, 제너레이터 컴프리헨션은 요소를 '지연 평가' 방식으로 처리하며, 이터레이터를 통해 요소에 접근할 때만 해당 요소를 메모리에 로드합니다. 이러한 방식은 큰 데이터셋을 처리할 때 메모리 부담을 크게 줄일 수 있습니다.
3.1.2 제너레이터 컴프리헨션의 활용
제너레이터 컴프리헨션을 사용하면 복잡한 연산을 담은 큰 리스트를 생성할 때도 메모리를 효율적으로 사용할 수 있습니다. 예를 들어, 매우 큰 범위의 숫자에 대해 복잡한 연산을 수행하는 경우에는 제너레이터 컴프리헨션을 사용하는 것이 더 좋습니다.
아래 예시에서는 간단하게 만들어본 제너레이터 컴프리헨션입니다. 형태가 리스트 컴프리헨션과 동일하여 튜플 컴프리헨션으로 오해할 수 있지만 해당 컴프리헨션은 메모리를 한꺼번에 사용하지 않는 제너레이터입니다.
gen = (i for i in range(100))
gen = (i for i in range(100))
3.2.3 다양한 환경에서의 사용
제너레이터 컴프리헨션은 파일 처리, 네트워크 통신, 데이터베이스 접근 등 다양한 환경에서 유용하게 사용됩니다. 특히, 데이터를 조각조각 읽어오는 경우에 제너레이터가 더 효율적이며, 메모리 사용량을 최소화할 수 있습니다.
파일처리는 다음과 같습니다. 대용량의 로그 파일이나 데이터 파일을 처리할 때, 파일 전체를 메모리에 로드하면 메모리 부족 문제가 발생할 수 있습니다. 이러한 경우 제너레이터 컴프리헨션을 사용하여 파일을 줄 단위로 읽고 처리할 수 있습니다.
gen = (process_line(line) for line in open('large_log_file.log'))
gen = (process_line(line) for line in open('large_log_file.log'))
이 예에서 process_line
은 각 줄을 처리하는 함수이며, 제너레이터 컴프리헨션은 파일을 한 줄씩 읽어 처리합니다.
네트워크통신시는 다음과 같습니다. 대용량의 데이터 스트림을 받아야 하는 경우에도 제너레이터 컴프리헨션을 활용할 수 있습니다. 데이터를 조각조각 받아서 처리하면, 메모리를 효율적으로 활용할 수 있습니다.
gen = (process_packet(packet) for packet in network_stream())
gen = (process_packet(packet) for packet in network_stream())
여기서 network_stream
은 네트워크 패킷을 하나씩 반환하는 함수이며, process_packet
은 각 패킷을 처리하는 함수입니다.
또한데이터베이스에서 큰 데이터셋을 조회할 때, 모든 결과를 메모리에 로드하면 메모리 부족 문제가 발생할 수 있습니다. 이 경우 제너레이터 컴프리헨션을 사용하여 데이터를 한 번에 한 레코드씩만 메모리에 로드할 수 있습니다.
gen = (process_record(record) for record in db_cursor)
gen = (process_record(record) for record in db_cursor)
여기서 db_cursor
은 데이터베이스 커서 객체이며, process_record
은 각 레코드를 처리하는 함수입니다. 이처럼 제너레이터 컴프리헨션은 다양한 환경에서 메모리 효율성을 향상시킬 수 있도록 돕습니다.
(번외) 시각화도구를 활용한 코드 이해
💡 해당 내용은 종이책에 실리지는 않는 내용입니다.
코드의 작동 원리를 더 깊게 이해하는 데에는 시각화 도구가 큰 도움이 될 수 있습니다. Python Tutor는 파이썬 코드의 실행 과정을 단계별로 분석하여 각 변수의 상태 변화를 시각적으로 보여주는 훌륭한 도구입니다. 이러한 도구를 활용하면 복잡한 코드의 작동 방식을 더 쉽게 이해할 수 있습니다.
특히, 이터레이터와 제너레이터와 같은 파이썬의 고급 기능을 학습할 때, Python Tutor를 사용하면 코드의 흐름을 더욱 명확하게 파악할 수 있습니다.
아래는 Python Tutor를 활용하여 제너레이터 함수의 작동 원리를 시각적으로 분석하는 방법입니다:
-
Python Tutor 웹사이트에 접속합니다. # https://pythontutor.com/
-
웹 페이지 중앙에 있는 코드 에디터 부분에 아래의 Python 코드를 붙여넣습니다.
def my_generator(data): for i in data: yield i my_list = [1, 2, 3, 4, 5] my_iterator = my_generator(my_list) for i in my_iterator: print(i) -
페이지 아래쪽에 있는 ‘Visualize Execution’ 버튼을 클릭합니다.
-
새로운 창에서 코드의 실행 과정이 단계별로 시각화되어 나타납니다. ‘prev’ 및 ‘next’ 버튼을 사용하여 코드 실행의 각 단계를 전후로 이동할 수 있습니다.
이 도구를 통해 제너레이터 함수의 실행 과정을 단계별로 따라가며, 각 단계에서 변수의 상태 변화를 직접 확인할 수 있습니다. 이를 통해 제너레이터의 작동 방식을 더욱 명확하게 이해할 수 있습니다.