[Python] yield 문이란 무엇인가 ?!
- 언어/Python
- 2019. 3. 25. 16:10
파이썬 함수 안에서 yield 키워드를 사용하는 함수를 호출하면, 그 함수는 생성기(Generator) 객체를 반환합니다.
yield문을 이해하기 위해 먼저 생성기 객체에 대해서 알아보겠습니다.
생성기(Generator)는 무엇일까요?
for문과 같은 반복문에서 사용할 값들을 생성하는 객체입니다. 생성기 객체의 next()를 ( Python3 에서는 _next_() ) 호출하면 yield 문까지 함수가 실행되고 실행이 중단됩니다. 다음에 next()를 다시 호출하면 중단된 지점 다음부터 다시 함수가 실행됩니다.
예제를 통해서 생성기를 이해해 보도록 하겠습니다:
def yield_func():
n = 0
while n < 3:
print('[yield_func] Start of loop. (n = {})'.format(n))
print('[yield_func] Before yield. (n = {})'.format(n))
yield n
n += 1
print('[yield_func] After yield. (n = {})'.format(n))
print('[yield_func] End of loop. (n = {})'.format(n))
y = yield_func()
print('[main] yield가 반환한 객체 : {}'.format(y))
print('[main] 첫번째 next() 리턴값 : {}\n'.format(y.__next__()))
print('[main] 두번째 next() 리턴값 : {}\n'.format(y.__next__()))
print('[main] 세번째 next() 리턴값 : {}\n'.format(y.__next__()))
print('[main] 네번째 next() 리턴값 : {}'.format(y.__next__()))
실행 결과는 다음과 같습니다:
[main] yield가 반환한 객체 : <generator object yield_func at 0x7fcf500c1750>
[yield_func] Start of loop. (n = 0)
[yield_func] Before yield. (n = 0)
[main] 첫번째 next() 리턴값 : 0
[yield_func] After yield. (n = 1)
[yield_func] End of loop. (n = 1)
[yield_func] Start of loop. (n = 1)
[yield_func] Before yield. (n = 1)
[main] 두번째 next() 리턴값 : 1
[yield_func] After yield. (n = 2)
[yield_func] End of loop. (n = 2)
[yield_func] Start of loop. (n = 2)
[yield_func] Before yield. (n = 2)
[main] 세번째 next() 리턴값 : 2
[yield_func] After yield. (n = 3)
[yield_func] End of loop. (n = 3)
Traceback (most recent call last):
File "ex1.py", line 16, in <module>
print('[main] 네번째 next() 리턴값 : {}'.format(y.__next__()))
StopIteration
생성기 객체의 next() 함수를 처음 호출하면 처음부터 yield 문까지 함수가 실행되고 중단됩니다. 이후에 next() 문이 다시 호출되면 yield 문 이후부터 실행을 재개하여 다시 yield문을 만나면 실행이 중단됩니다. 반복이 종료되어 더 이상 yield문을 만날 기회가 없다면, StopIteration 예외를 발생시켜서 종료합니다.
그래서 yield문은 어디에 써먹는 걸까요?
아래 예제는 정수 배열을 만들어서 반환하는 함수입니다. 메인에서는 아주 큰 정수 값을 이 함수의 파라미터로 넣어서 반환된 배열의 각 요소의 합을 구합니다.
def custom_range(n):
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
return nums
### n 이 5이면 [0, 1, 2, 3, 4] 를 반환합니다.
sum_of_custom_range = sum(custom_range(1400000000))
### [0, 1, ..., 1399999998, 1399999999] 배열의 각 요소의 합을 구합니다.
이 경우 메모리에는 nums 라는 배열을 저장하기 위한 메모리가 항상 상주해있어야 합니다. 단순한 코드를 수행하기 위해 많은 메모리가 소비되어야 하는 구조입니다.
여기에서 yield문을 사용하여 생성기를 사용하는 코드를 적용하면 메모리의 사용을 대폭 줄일 수 있습니다. 다음과 같이 말입니다:
def custom_range_with_yield(n):
num = 0
while num < n:
yield num
num += 1
sum_of_custom_range_with_yield = sum(custom_range_with_yield(1400000000))
이렇게 yield문을 사용하면 지연된 생성 (lazy generation)을 수행함으로 모든 요소를 메모리에 올려둘 필요 없이 그때 그때 필요한 요소들을 생성하여 sum을 수행합니다. 따라서 메모리도 더 효율적으로 사용할 수 있습니다.
하지만, 항상 list보다 생성기가 좋은 것은 아닙니다! 프로그램이 아래와 같이:
sum_of_custom_range_with_yield_1 = sum(custom_range_with_yield(1400000000))
sum_of_custom_range_with_yield_2 = sum(custom_range_with_yield(1400000000))
생성기를 통해서 똑같은 연산을 두 번 수행하는 경우에는 더 많은 연산이 필요하기 때문입니다. 이 경우 아래와 같이 리스트를 메모리에 상주해 두는 편이 더 나을 수도 있습니다.
nums = list(custom_range_with_yield(1400000000))
s = sum(nums)
p = product(nums)
하지만, 메모리의 중요성이 크지 않고, 같은 연산을 반복하는 것이 더 값 싼 경우라면 위와 같이 생성기를 사용하는 편이 더 나을 수도 있습니다.
지금까지는 yield문이 단독으로 나와서 값을 출력했습니다. 다음 포스팅에서는 대입 연산자( = )의 오른쪽에 yield가 나오는 경우 (코루틴과 yield 표현식) 에 대해서 포스팅해보겠습니다.
레퍼런스
'언어 > Python' 카테고리의 다른 글
[Python] 파이썬 작업 실행시간 측정하기 (0) | 2019.04.24 |
---|---|
[Python] 리스트 내포 / 이중 리스트 내포 with IF문 (0) | 2019.03.22 |
[Python] sys 모듈을 이용하여 표준입력으로부터 입력 받기 (0) | 2019.03.20 |
[Python] sys 모듈을 이용하여 실행 인자값 받기 ! (0) | 2019.03.20 |