파이썬의 제너레이터
이 문서에서는 파이썬의 제너레이터에 대해 설명합니다.
YouTube Video
파이썬의 제너레이터
개요
파이썬의 제너레이터는 반복 처리를 효율적으로 수행할 수 있는 강력한 기능이며, 반복자의 일종입니다. 제너레이터를 사용하면 대용량 데이터 처리 시 메모리 효율적인 코드를 작성할 수 있습니다.
제너레이터란?
파이썬에서 제너레이터는 yield
키워드로 정의되며, 한 번에 하나씩 값을 생성하는 특별한 함수입니다. 실행을 중단하면서 현재 상태를 보존하고, 나중에 다시 실행을 재개할 수 있는 것이 특징입니다.
yield
의 기초
yield
는 값을 반환하면서 함수의 실행을 일시 중단시키는 키워드입니다.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- 이런 함수를 호출하면, 값을 하나씩 반환하는 제너레이터 객체가 생성됩니다.
next()
를 호출했을 때 다음 값이 없으면StopIteration
오류가 발생합니다.
next()
와 StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- 이렇게
StopIteration
오류를 명시적으로 처리하면, 제너레이터가 완료되었는지 감지할 수 있습니다.
send(value)
send(value)
를 호출하면 제너레이터가 다시 시작되고, value
가 yield
표현식의 위치로 전달됩니다. 보내진 값은 제너레이터 측에서 yield
표현식의 반환값으로 받을 수 있습니다. 처음 호출할 때는 send(value)
로 None
이외의 값을 보낼 수 없으므로, 반드시 next()
나 send(None)
을 사용해야 합니다.
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
send(10)
을 사용하면 제너레이터의yield
가 10을 반환하는 표현식이 되며, 10이x
에 할당됩니다.
throw()
throw
를 호출하면 제너레이터가 다시 시작되며, 일시 중단된 yield
위치에서 예외가 발생합니다. 제너레이터 내부에서 예외를 처리하여 처리를 계속할 수 있습니다. 예외가 처리되지 않으면 외부로 전파되어 제너레이터가 종료됩니다.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- 이 코드에서는
throw
를 호출하여 제너레이터에 예외를 주입합니다. 제너레이터 측에서는 예외를 처리하고recovered
를 반환합니다.
close()
close()
를 호출하면 제너레이터가 종료됩니다. 제너레이터 내부에서 finally
를 사용하여 정리 작업을 수행할 수 있습니다. close()
호출 후 next()
또는 send()
를 호출하면 StopIteration
오류가 발생합니다.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- 이 코드는
close()
를 호출하면 제너레이터가 종료되고,finally
에서 정리 작업이 실행되는 것을 보여줍니다.
yield from
yield from
은 서브제너레이터에 위임할 때 사용하는 구문입니다. 이것은 제너레이터 내부에서 또 다른 제너레이터를 호출하고, 그 모든 값을 외부 스코프로 전달하는 간단한 방법입니다.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- 이 코드는
yield from
을 사용하여 서브제너레이터의 모든 값을 외부 제너레이터로 위임한 다음,3
을 반환합니다.
이터레이터와의 관계
제너레이터는 내부적으로 __iter__()
와 __next__()
를 구현하고 있어 이터레이터의 한 종류입니다. 따라서 for
루프 등 반복 가능한 객체에 사용할 수 있습니다.
for
루프와의 연동
파이썬의 for
루프는 내부적으로 next()
를 사용해 값을 자동으로 가져옵니다.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
이 방식에서는 StopIteration
처리도 자동으로 이루어집니다.
무한 제너레이터 만들기
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
무한 반복이 가능한 제너레이터를 만들 수 있지만, 사용할 때에는 주의가 필요합니다.
제너레이터 표현식
괄호를 사용해 작성하는 제너레이터 표현식을 통해 리스트 내포와 유사한 문법으로 제너레이터를 정의할 수 있습니다.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
리스트 내포와 달리 모든 요소를 한 번에 메모리로 불러오지 않기 때문에 더 메모리 효율적입니다.
제너레이터에서의 예외 처리
제너레이터 내에서 예외가 발생할 수 있습니다. 이 경우 일반 파이썬 코드와 마찬가지로 try-except
를 사용합니다.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
이 예제에서는 0으로 나눌 때 적절한 예외 처리가 이루어집니다.
제너레이터의 스택 트레이스
제너레이터 내부에서 예외가 발생하면, 제너레이터가 다시 실행될 때 그 예외가 발생합니다.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- 이 제너레이터는 먼저
1
을 반환합니다. 다시 실행할 때 발생한 오류가 포착되어 에러 메시지로 표시됩니다.
제너레이터 활용 예시
대용량 파일을 한 줄씩 읽기
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- 이 함수는 이터레이터로 텍스트 파일을 한 줄씩 읽고, 각 줄의 공백을 제거한 뒤 제너레이터로 반환하여, 대용량 파일을 적은 메모리로 처리할 수 있게 해줍니다.
피보나치 수열 제너레이터
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- 이 코드는 제너레이터를 사용하여 상한값보다 작은 피보나치 수를 순차적으로 생성하고,
for
반복문을 통해 출력합니다.
활용 예시
제너레이터는 다음과 같은 상황에서도 사용할 수 있습니다.
- 대용량 CSV 또는 로그 파일 순차 처리
- API 페이지네이션 처리
- 스트리밍 데이터 처리 (예: Kafka, IoT 장치)
요약
개념 | 주요 포인트 |
---|---|
yield |
중단하면서 값을 반환 |
제너레이터 함수 | yield 를 포함하고 호출 시 이터레이터를 반환하는 함수 |
장점 | 메모리 효율적이며 대용량 데이터 처리에 적합함 |
제너레이터 표현식 | (x for x in iterable) 과 같은 간결한 문법 사용 가능 |
제너레이터를 사용하면 메모리를 절약하면서도 대용량 데이터를 효율적으로 처리할 수 있으며, 코드도 간결하게 유지할 수 있습니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.