yield
yield 키워드는 파이썬에서 제너레이터(generator)를 생성하는 데 사용된다. 제너레이터는 이터레이터의 일종으로, 일반적인 함수와는 달리 값을 한 번에 반환하지 않고, 하나씩 반환하면서 실행 상태를 유지한다. 이를 통해 메모리 사용을 최적화하고, 큰 데이터셋을 다룰 때 효율적인 처리를 가능하게 한다.
제너레이터와 yield의 개념
- 제너레이터 함수:
- yield 키워드를 포함한 함수는 제너레이터 함수가 된다.
- 제너레이터 함수는 호출될 때 함수 전체가 실행되는 것이 아니라, 이터레이터 객체를 반환한다.
- yield 키워드:
- 함수가 실행을 중단하고 호출자에게 값을 반환한다.
- 중단된 위치와 상태를 기억하고, 이후 다시 호출되면 그 위치에서 실행을 재개한다.
- 여러 번 호출될 수 있으며, 그때마다 yield로 지정된 값이 반환된다.
제너레이터 함수의 동작 예시
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # 1 출력
print(next(gen)) # 2 출력
print(next(gen)) # 3 출력
위 예제에서 simple_generator 함수는 세 개의 yield 구문을 가지고 있다. 이 함수는 호출될 때 제너레이터 객체를 반환하며, next() 함수가 호출될 때마다 순차적으로 yield에 지정된 값들을 반환한다.
제너레이터의 장점
- 메모리 효율성:
- 한 번에 모든 값을 메모리에 올리지 않고, 필요한 값만 생성하여 반환하기 때문에 메모리 사용이 효율적이다.
- 특히, 매우 큰 데이터셋을 다룰 때 유용하다.
- 지연 평가(Lazy Evaluation):
- 제너레이터는 요청이 있을 때 값들을 생성한다.
- 이로 인해 불필요한 계산을 피할 수 있으며, 프로그램의 효율성을 높인다.
제너레이터 표현식
제너레이터는 함수뿐만 아니라 표현식으로도 생성할 수 있다. 제너레이터 표현식은 리스트 컴프리헨션과 유사하지만, [] 대신 ()를 사용한다.
gen_exp = (x * x for x in range(10))
for num in gen_exp:
print(num)
위 예제에서 제너레이터 표현식은 0부터 9까지의 제곱 값을 순차적으로 생성한다.
제너레이터의 활용 예
제너레이터는 파일 처리, 네트워크 프로그래밍, 대규모 데이터 처리 등 다양한 분야에서 활용된다. 예를 들어, 큰 파일을 한 줄씩 읽어들이는 경우 제너레이터를 사용하면 메모리 효율성을 크게 높일 수 있다.
def read_large_file(file_path):
with open(file_path, 'r') as file:
while True:
line = file.readline()
if not line:
break
yield line
for line in read_large_file('large_file.txt'):
print(line)
이 예제에서 read_large_file 함수는 큰 파일을 한 줄씩 읽어들이며, 메모리 효율적으로 처리할 수 있다.
yield 키워드는 제너레이터를 생성하고, 메모리 효율성을 높이며, 지연 평가를 통해 프로그램의 성능을 최적화하는 데 중요한 역할을 한다. 제너레이터와 yield를 활용하면 더 효율적이고, 확장 가능한 파이썬 코드를 작성할 수 있다.
코루틴(coroutine)
코루틴(coroutine)은 일반적인 함수 호출 패턴과 다르게, 호출자와 협력적으로 실행 제어를 주고받을 수 있는 특별한 형태의 서브루틴이다. 파이썬에서는 제너레이터를 이용해 코루틴을 구현할 수 있다. 코루틴은 주로 비동기 프로그래밍, 이벤트 기반 프로그래밍 등에서 사용된다.
코루틴의 기본 동작
코루틴은 일반적인 제너레이터와 비슷하지만, 단순히 값을 반환하는 것 외에 값을 함수로 보내거나, 특정 이벤트를 기다리는 동안 실행을 일시 중지할 수 있다.
- 코루틴 함수 정의:
- 코루틴은 일반 함수처럼 정의되지만, yield 키워드를 사용해 실행 상태를 제어한다.
- yield의 역할:
- 코루틴에서 yield는 값을 반환할 뿐 아니라, 외부에서 값을 받아들이는 역할도 한다.
- yield 표현식은 외부에서 보내진 값을 받아 변수에 저장할 수 있다.
- 코루틴 객체:
- 코루틴 함수를 호출하면 코루틴 객체가 생성된다.
- 코루틴 객체는 제너레이터 객체와 유사하게 동작하지만, send() 메서드를 이용해 값을 보낼 수 있다.
코루틴 상태
코루틴은 특정 상태를 가지며, 그 상태에 따라 동작이 다르게 진행된다. 파이썬의 코루틴은 다음과 같은 주요 상태를 가질 수 있다:
- Created (생성됨):
- 코루틴 객체가 생성되었지만, 아직 실행이 시작되지 않은 상태이다.
- 이 상태에서는 코루틴이 아무 작업도 수행하지 않는다.
- 예: coro = coroutine_function()
- Running (실행 중):
- 코루틴이 현재 실행 중인 상태이다.
- 이 상태에서 코루틴은 코드 블록을 실행하며, yield 표현식에서 중단될 때까지 계속된다.
- 예: next(coro) 또는 coro.send(value)
- Suspended (중단됨):
- 코루틴이 yield 표현식에서 중단된 상태이다.
- 이 상태에서는 코루틴이 멈추고, 다음 next() 또는 send() 호출까지 기다린다.
- 중단된 지점에서 코루틴은 값을 반환할 수 있으며, 외부에서 값을 받을 준비가 되어 있다.
- 예: yield가 실행된 후
- Closed (종료됨):
- 코루틴이 완료되거나, close() 메서드를 통해 종료된 상태이다.
- 이 상태에서는 더 이상 코루틴을 실행할 수 없다.
- 예: coro.close() 또는 코루틴의 모든 코드가 실행된 후
코루틴 예제
def simple_coroutine():
print("Coroutine started")
x = yield
print(f"Received: {x}")
y = yield x + 1
print(f"Received: {y}")
yield y + 1
# 코루틴 객체 생성 (Created 상태)
coro = simple_coroutine()
# 코루틴 실행 시작 (Running 상태)
print(next(coro)) # "Coroutine started" 출력, 첫 번째 yield에서 중단 (Suspended 상태)
# 코루틴 재개 (Running 상태)
print(coro.send(10)) # "Received: 10" 출력, 두 번째 yield에서 중단 (Suspended 상태)
# 코루틴 재개 (Running 상태)
print(coro.send(20)) # "Received: 20" 출력, 세 번째 yield에서 중단 (Suspended 상태)
# 코루틴 종료 (Closed 상태)
coro.close()
주요 메서드와 동작 coro = simple_coroutine() 호출 시, 코루틴은 Created 상태가 된다.
next():
- next(coro) 호출 시, 코루틴이 실행을 시작하여 Running 상태가 되고, 첫 번째 yield에서 중단되어 Suspended 상태가 된다
- 코루틴의 다음 yield 지점까지 실행을 진행한다.
- 코루틴이 처음 시작할 때는 send() 대신 next()를 사용해야 한다.
send(value):
- coro.send(10) 호출 시, 코루틴이 다시 Running 상태가 되어 두 번째 yield에서 중단되고 Suspended 상태가 된다.
- yield 표현식으로 값을 보낸다.
- yield가 있는 위치에서 실행이 재개되고, yield 표현식이 value 값을 반환한다.
- coro.send(20) 호출 시, 코루틴이 다시 Running 상태가 되어 세 번째 yield에서 중단되고 Suspended 상태가 된다.
close():
- 코루틴을 종료한다.
- GeneratorExit 예외가 발생하고, 이를 통해 정리 작업을 수행할 수 있다.
- coro.close() 호출 시, 코루틴이 Closed 상태가 되어 더 이상 실행할 수 없다.
throw(type, [value, [traceback]]):
- 코루틴 실행 중 예외를 발생시킨다.
- 예외는 현재 실행 중인 yield 표현식에서 발생하며, 이를 통해 오류 처리를 할 수 있다.
코루틴의 상태 관리
코루틴의 상태를 관리하면서 다양한 비동기 작업을 수행할 수 있다. 다음과 같은 메서드를 활용해 코루틴의 상태를 관리할 수 있다:
- next(): 코루틴을 실행하거나 재개하여 다음 yield 지점까지 진행한다.
- send(value): yield 표현식에 값을 보내고, 코루틴을 재개한다.
- close(): 코루틴을 종료하고 Closed 상태로 만든다.
- throw(type, [value, [traceback]]): 코루틴에 예외를 발생시켜 예외 처리를 수행한다.
코루틴은 제너레이터의 확장으로, 외부에서 값을 보내거나, 특정 시점에 실행을 일시 중지할 수 있는 기능을 제공한다. 이를 통해 비동기 작업이나 이벤트 기반 프로그램에서 협력적 멀티태스킹을 구현할 수 있다. 코루틴의 주요 메서드(next(), send(), close(), throw())를 이해하고 활용하면, 복잡한 비동기 로직을 효율적으로 처리할 수 있다.
코루틴 초기화 데커레이터
코루틴을 보다 편리하게 사용할 수 있도록 도와주는 데커레이터가 있다. 이 데커레이터는 코루틴 함수가 호출될 때 자동으로 next()를 호출하여 코루틴을 초기화한다. 이로 인해 사용자는 코루틴을 처음 사용할 때 직접 next()를 호출할 필요가 없어진다.
코루틴 초기화 데커레이터
def coroutine(func):
def wrapper(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # 코루틴 초기화
return gen
return wrapper
이 데커레이터는 다음과 같은 방식으로 동작한다:
- 데커레이터는 코루틴 함수를 래핑한다.
- 래핑된 함수가 호출될 때, 코루틴 객체를 생성하고 next()를 호출하여 코루틴을 초기화한다.
- 초기화된 코루틴 객체를 반환한다.
@coroutine
def simple_coroutine():
print("Coroutine started")
while True:
x = yield
print(f"Received: {x}")
# 코루틴 생성 및 초기화가 자동으로 수행됨
coro = simple_coroutine()
# 코루틴에 값 보내기
coro.send(10) # "Received: 10" 출력
coro.send(20) # "Received: 20" 출력
단계별 설명
- 데커레이터 정의:
- coroutine 데커레이터를 정의한다. 이 데커레이터는 코루틴 함수를 인수로 받아들이고, 래핑된 함수를 반환한다.
- 래핑된 함수 내부에서 코루틴 객체를 생성하고 next()를 호출하여 초기화한다.
- 코루틴 정의:
- @coroutine 데커레이터를 사용하여 simple_coroutine 함수를 정의한다.
- 이 함수는 무한 루프를 통해 값을 받아들이고 출력한다.
- 코루틴 사용:
- coro = simple_coroutine()를 호출하면 코루틴 객체가 생성되고, 자동으로 초기화된다.
- coro.send(10)을 호출하여 값을 코루틴에 보내면, yield 표현식에서 값을 받아들이고 출력한다.
- 이 과정을 통해 코루틴이 계속 실행된다.
상태 유지 및 종료 처리
@coroutine
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
# 코루틴 생성 및 초기화
acc = accumulator()
# 코루틴에 값 보내기
print(acc.send(10)) # 출력: 10
print(acc.send(20)) # 출력: 30
print(acc.send(5)) # 출력: 35
# 코루틴 종료
acc.send(None)
단계별 설명
- 데커레이터 사용:
- @coroutine 데커레이터를 사용하여 accumulator 함수를 정의한다.
- 이 함수는 total 변수를 유지하며, 외부에서 값을 받아 누적한다.
- 코루틴 초기화:
- acc = accumulator()를 호출하여 코루틴 객체를 생성하고 초기화한다.
- 값 누적 및 출력:
- acc.send(10)을 호출하여 값을 보내면, 누적된 합계인 total을 반환한다.
- 이 과정을 통해 누적 합계를 계산하고 반환한다.
- 코루틴 종료:
- acc.send(None)을 호출하여 value가 None일 때, 루프를 종료하고 코루틴을 종료한다.
이와 같이 코루틴 초기화 데커레이터를 사용하면, 코루틴을 보다 편리하게 초기화하고 사용할 수 있다. 이를 통해 복잡한 비동기 작업을 보다 효율적으로 처리할 수 있다.
코루틴 종료와 예외처리
코루틴의 종료와 예외 처리는 코루틴의 올바른 사용과 관리를 위해 중요한 부분이다. 파이썬에서는 코루틴을 종료하고 예외를 처리하기 위한 여러 가지 방법을 제공한다. 이들 방법을 통해 코루틴의 상태를 제어하고, 예상치 못한 상황에 대한 처리를 할 수 있다.
코루틴 종료
코루틴을 종료하는 방법은 크게 두 가지가 있다:
- close() 메서드 사용:
- 코루틴 객체의 close() 메서드를 호출하면 GeneratorExit 예외가 발생하고, 코루틴이 종료된다.
- 코루틴 내에서 GeneratorExit 예외를 잡을 수 있으며, 이 예외가 발생하면 정리 작업을 수행할 수 있다.
- return 문 사용:
- 코루틴 함수 내에서 return 문을 사용하면 코루틴이 종료된다.
- 이 경우 코루틴은 StopIteration 예외를 발생시킨다.
코루틴 종료 예제
def simple_coroutine():
print("Coroutine started")
try:
while True:
x = yield
print(f"Received: {x}")
except GeneratorExit:
print("Coroutine ending")
# 코루틴 생성 및 초기화
coro = simple_coroutine()
next(coro)
# 코루틴에 값 보내기
coro.send(10) # "Received: 10" 출력
coro.send(20) # "Received: 20" 출력
# 코루틴 종료
coro.close() # "Coroutine ending" 출력
예외 처리
코루틴 내에서 예외를 처리하는 방법은 여러 가지가 있다:
- try-except 블록 사용:
- 코루틴 내에서 try-except 블록을 사용하여 발생하는 예외를 처리할 수 있다.
- 이는 일반적인 예외 처리와 동일한 방식이다.
- throw() 메서드 사용:
- 외부에서 코루틴에 예외를 던질 때 사용한다.
- throw() 메서드는 특정 예외를 코루틴의 현재 yield 지점에서 발생시키며, 이를 통해 예외를 처리할 수 있다.
예외 처리 예제
def exception_handling_coroutine():
print("Coroutine started")
try:
while True:
try:
x = yield
print(f"Received: {x}")
except ValueError:
print("ValueError handled")
except GeneratorExit:
print("Coroutine ending")
# 코루틴 생성 및 초기화
coro = exception_handling_coroutine()
next(coro)
# 코루틴에 값 보내기
coro.send(10) # "Received: 10" 출력
coro.send(20) # "Received: 20" 출력
# 코루틴에 예외 던지기
coro.throw(ValueError) # "ValueError handled" 출력
# 코루틴 종료
coro.close() # "Coroutine ending" 출력
코루틴의 종료와 예외 처리는 코루틴을 안정적으로 운영하는 데 필수적인 요소이다. close() 메서드와 throw() 메서드를 통해 코루틴을 제어하고, try-except 블록을 통해 예외를 처리할 수 있다. 이러한 방법들을 활용하면 코루틴을 보다 유연하고 안정적으로 사용할 수 있다.
yield from
yield from 문은 파이썬 3.3에서 도입된 구문으로, 제너레이터 내에서 다른 제너레이터나 이터러블의 항목들을 효율적으로 위임(delegation)할 수 있도록 한다. yield from은 제너레이터를 쉽게 구성하고, 코드의 가독성을 높이며, 반복 처리를 간소화하는 데 유용하다.
yield from의 정의와 의미
yield from은 기본적으로 다음 두 가지 역할을 한다:
- 위임:
- 현재 제너레이터의 제어를 다른 제너레이터나 이터러블에 위임한다.
- yield from iterable은 iterable의 모든 항목을 하나씩 yield 한다.
- 값 전파:
- 서브 제너레이터에서 반환된 값을 상위 제너레이터로 전달한다.
- 서브 제너레이터에서 발생한 예외를 상위 제너레이터로 전달한다.
기본 예제
def generator1():
yield 1
yield 2
yield 3
def generator2():
yield from generator1()
yield 4
yield 5
for value in generator2():
print(value)
'''
1
2
3
4
5
'''
위 예제에서 generator2는 generator1에 있는 항목들을 yield from을 통해 모두 위임받아 출력한 후, 추가적인 값을 생성한다.
yield from의 동작 방식
yield from iterable은 다음과 같은 작업을 수행한다:
- 이터레이터 초기화:
- iterable이 이터레이터가 아니라면, iter(iterable)을 호출하여 이터레이터를 생성한다.
- 값 위임:
- iterable의 항목을 하나씩 yield합니다. 이는 일반적인 for 루프와 유사하다.
- 예외 전파:
- 이터레이터에서 예외가 발생하면, 그 예외를 상위 제너레이터로 전파한다.
- 최종 반환값 처리:
- 서브 제너레이터가 return value를 통해 반환값을 제공하면, 그 값을 yield from 표현식의 값으로 반환한다.
트리 순회
class Node:
def __init__(self, value, children=None):
self.value = value
self.children = children if children is not None else []
def traverse_tree(node):
yield node.value
for child in node.children:
yield from traverse_tree(child)
# 트리 구조 생성
tree = Node(1, [
Node(2, [
Node(4),
Node(5)
]),
Node(3, [
Node(6),
Node(7)
])
])
# 트리 순회
for value in traverse_tree(tree):
print(value)
'''
1
2
4
5
3
6
7
'''
위 예제에서 traverse_tree 함수는 yield from을 사용하여 각 노드의 자식들을 재귀적으로 순회한다.
yield from은 제너레이터를 쉽게 구성하고 중첩된 이터레이터의 값을 효율적으로 처리할 수 있는 강력한 도구이다. 이를 통해 복잡한 이터레이션 작업을 간단하게 구현할 수 있으며, 코드의 가독성과 유지 보수성을 높일 수 있다. yield from을 사용하면 다른 제너레이터나 이터러블의 항목들을 손쉽게 위임하고, 반환값과 예외를 효율적으로 처리할 수 있다.
모든 코드는 github에 저장되어 있습니다.
'Python study' 카테고리의 다른 글
asynico를 이용한 동시성 (1) | 2024.06.06 |
---|---|
Future를 이용한 동시성 (1) | 2024.06.04 |
콘텍스트 관리자와 else 블록 (0) | 2024.06.04 |
제너레이터 (Generator) (1) | 2024.05.29 |
연산자 오버로딩 (0) | 2024.05.29 |