오래전에 C# Coroutine을 살펴본적이 있다. Coroutine을 실행하면, yield를 만날 때까지 실행되다가 yield를 만나면 리턴된다. 특이한 점은 다음 실행시, yield 부분부터 실행된다는 점이다. Unity는 초당 수십번 루프를 돌며 프레임 렌더링등을 처리하기 때문에, 단일 쓰레드에서 Coroutine을 마치 새로운 쓰레드를 생성한 것처럼 사용할 수 있었다.
이처럼 C#의 Coroutine과 유사하게 Python 에서도 yield를 사용할 수 있다. 보통 함수에 리턴대신 yield를 사용해주면 그걸로 끝이다. 이렇게하면, Python에서 이 함수를 컨트롤 하는 generator를 리턴하게 된다. 이 generator는 __iter__(), __next__()가 구현되어있어, iterable하며, next()를 호출하면 yield로 리턴된 곳부터 실행된다.
def fib():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
def finite_generator():
for n in range(0, 5):
print("in generator: number generated")
yield n
def main():
fib_gen = fib()
for i in range(1, 10):
print(next(fib_gen))
for n in finite_generator():
print(f"returnd value : {n}")
if __name__ == "__main__":
main()
첫번째 함수는 피보나치 수열을 generator로 구현한 것이다. 무한루프로 next()가 호출될 때마다 수열의 다음값을 리턴한다. main에서는 테스트로 10까지만 돌렸다.
두번째 예제인 finit_generator는 일정 회수만 돌고 종료된다. generator 자체가 iterable하기 때문에, 이를 바로 list처럼 사용도 가능하다. 예제의 main에선 for loop에 바로 사용했다.
출력결과는 다음과 같다.
1
1
2
3
5
8
13
21
34
in generator: number generated
returnd value : 0
in generator: number generated
returnd value : 1
in generator: number generated
returnd value : 2
in generator: number generated
returnd value : 3
in generator: number generated
returnd value : 4
확실히 알아야 하는건, 값만 만들어 내는게 아니고 두번째 예제처럼 generator 함수내에서 print 출력등, 원하는 작업이 가능하다는 사실이다. 루프와 결합되면, C#의 coroutine처럼 사용될 수 있음을 예상할 수 있다.
yield expression의 정확한 설명은 공식 문서를 참조하기 바란다. generator에 대한 PEP-255 문서도 참고하기 좋다.