파이썬 제너레이터

Jaehyeong Kwon·2023년 2월 23일
0

파이썬

목록 보기
1/2

파이썬에서 제너레이터(generator)를 만드는 데 이용하는 yield 키워드에 대해 알아보겠습니다.


yield 키워드

대부분의 프로그래밍 언어에서는 일반적으로 어떤 결과 값을 return 키워드를 이용하여 반환합니다. 하지만 파이썬에서는 yield 키워드를 이용하여 다소 다른 방법으로 결과 값을 제공할 수 있습니다.

간단한 예제 코드를 통해 알아 보도록 하겠습니다.

숫자 1, 2, 3 을 결과 값으로 반환하는 함수를 작성해보겠습니다.

def return_number():
	return list("123")
    

위 함수를 yield 키워드를 이용해서 작성해보겠습니다.

def yield_number():
	yield "1"
    yield "2"
    yield "3"

가장 눈에 두드러지는 차이는 return 키워드를 사용할 때는 한 번만 결과값을 제공하는 데, yield 키워드는 결과값을 여러 번 나누어서 제공합니다.

for 루프를 이용하여 함수를 호출하여 얻은 결과를 화면에 출력해보겠습니다.

for i in return_number():
	print(i)
1
2
3
for i in yield_number():
	print(i)
1
2
3

함수를 사용하는 측면에서 두 함수의 차이는 크게 나타나지 않습니다.
함수를 호출한 결과 값을 출력하여 각 함수가 무엇을 반환하는지 알아보겠습니다.

>>> print(return_number())
['1', '2', '3']
>>> print(return_number())
<generator object yield_number at 0x7f4ed03e6039>

return_number() 함수는 리스트 (list) 를 반환하고, yield_number() 함수는 제너레이터를 반환하는 것을 알 수 있습니다. 그렇다면 제너레이터란 무엇일까요?


제너레이터(generator)

파이썬에서 제너레이터는 여러 개의 데이터를 미리 만들어 놓지 않고 필요할 때마다 즉석에서 하나씩 만들어낼 수 있는 객체를 의미합니다.
예를 들어, 위에서 작성한 예제 코드를 알파벳 하나를 만드는데 1초가 걸리도록 해보겠습니다.

import time 

def return_number():
	numbers = []
    for i in "123":
    	time.sleep(1)
        numbers.append(i)
    return numbers

위 함수를 호출한 결과를 for 루프로 돌려보면 3초가 흐른 뒤 1, 2, 3 이 한 번에 출력 되는 것을 볼 수 있습니다.

for i in return_number():
	print(i)
# 3초 결과 
1
2
3

이 번에는 yield 키워드를 이용해서 동일한 결과 값을 제공하는 함수를 작성해보겠습니다.

import time 

def yield_number():
	for i in "123":
    	time.sleep(1)
        yield i

위 함수를 호출한 결과를 for 루프로 돌려보면 1초마다 결과가 출력되는 것을 확인할 수 있습니다.

for i in return_number():
	print(i)
# 1초 경과 
1
# 1초 경과
2
# 1초 경과
3

만약에 숫자의 개수가 세개가 아니라 만개, 십만개가 되는 경우에는 첫 번째 방식에서는 자료의 길이에 비례하게 첫번째 결과 값을 얻는데 오래 걸릴 것입니다. 하지만 두번째 방식에서는 항상 일 초가 걸릴 것입니다. 즉, 제너레이터를 통해서는 결과값을 나누어서 얻을 수 있기 때문에 성능 측면에서 큰 이점이 있습니다.

다시 한번 보자면 메모리 효율 측면에서도 이 두가지 방식은 큰 차이를 보입니다. return 키워드를 사용할 때는 모든 결과 값을 메모리에 올려놓아야 하는 반면, yield 키워드를 사용할 때는 결과 값을 하나씩 메모리에 올려놓습니다.

제너레이터(generator)는 이러한 특성 때문에 게으른 반복자(lazy iterator)라고도 불립니다. 이러한 특성을 잘 활용한다면 좀 더 효율적인 프로그램을 작성할 수 있습니다.

특히 메모리에 한번에 올리기에는 부담스러운 대용량의 파일을 읽거나, 스트림 데이터를 처리하는 데 상당히 유용하게 사용될 수 있습니다.


무한 데이터 생산

제너레이터를 사용하면 이론적으로 무한한 데이터를 계속해서 만들어낼 수 있습니다.
예를 들어, 숫자 1, 2, 3 를 계속해서 제공하는 함수를 작성해보겠습니다.

def yield_infinite_number():
	while True:
    	yield "1"
        yield "2"
        yield "3"

이 함수를 호출한 결과를 for 루프로 돌리면 반복해서 숫자가 출력이 될 것입니다. 이렇게 데이터를 무한하게 제공하는 함수는 사실 yield 키워드가 없이 작성하는 것은 불가능에 가깝습니다. 컴퓨터의 물리적인 메모리에는 언제나 한계가 있으며 아무리 큰 리스트를 만들더라도 이 한계를 초과할 수 없기 때문입니다.


yield from

제너레이터를 반환하는 함수를 작성하다보면 아래와 같이 리스트를 제너레이터로 변환해야 할 일이 자주 생깁니다.

def yield_number():
	for i in ["1", "2", "3"]:
    	yield i

yield from 을 사용하면 리스트를 바로 제너레이터로 변환할 수 있어 편리합니다.

def yield_number():
	yield from ["1", "2", "3"]

Generator comprehension

지금까지는 함수 안에서 yield 키워드를 이용하여 제너레이터를 만들어내는 방법에 대해서 알아보았습니다. 이번에는 제너레이터를 만드는 또 다른 방법인 제너레이터 표현식(Generator comprehension)에 대해서 배워보도록 하겠습니다.

리스트 표현식(list comprehension)과 사용 방법은 매우 유사합니다. 차이점은 리스트는 대괄호를 이용하고 제너레이터 표현식은 소괄호를 사용한다는 것입니다.

profile
나무와 같이 성장하는 사람

0개의 댓글