제너레이터는 이터레이터를 생성해 주는 함수이다.
제너레이터와 이터레이터의 차이첨
- 이터레이터 : 클래스에 __iter__, __next__, __getitem__ 메서드를 구현해야 한다.
- 제너레이터 : yield 키워드를 사용한다. 제너레이터 생성이 더 간편한 편이다.
제너레이터는 발생자 라고도 부른다.
제너레이터와 yield
함수에서 yield를 사용하면 함수가 제너레이터가 되며 yield에는 값을 지정해서 사용한다.
표현 방법
yield 값
yield 키워드를 사용하면 해당 함수와는 달리 함수를 호출해도 함수 내부의 코드가 실행되지 않는다.
def test():
print("함수가 호출되었습니다.")
yield "test"
print("A")
print(test())
# 결과값
# A
# <generator object test at 0x7fd191ef9ee0>
print("B")
print(test())
# 결과값
# B
# <generator object test at 0x7fd191ef9ee0>
일반 함수라면 "함수가 호출되었습니다." 라는 문자열이 출력되어야 하지만 출력이 되지 않는다.
그 이유는 제너레이터 함수는 이터레이터를 반환해주기 때문이다.
def number_generator():
yield 0
yield 1
yield 2
for i in number_generator():
print(i)
# 결과값
# 0
# 1
# 2
제너레이터 객체가 이터레이터인지 확인해보기
g = number_generator()
print(type(g))
g
# 결과값
# <class 'generator'>
# <generator object number_generator at 0x7fd1b1383b50>
dir(g)
# 결과값
# ['__class__',
# '__del__',
# '__delattr__',
# '__dir__',
# '__doc__',
# '__eq__',
# '__format__',
# '__ge__',
# '__getattribute__',
# '__gt__',
# '__hash__',
# '__init__',
# '__init_subclass__',
# '__iter__',
# '__le__',
# '__lt__',
# '__name__',
# '__ne__',
# '__new__',
# '__next__',
# '__qualname__',
# '__reduce__',
# '__reduce_ex__',
# '__repr__',
# '__setattr__',
# '__sizeof__',
# '__str__',
# '__subclasshook__',
# 'close',
# 'gi_code',
# 'gi_frame',
# 'gi_running',
# 'gi_yieldfrom',
# 'send',
# 'throw']
g.__next__()
# 0
g.__next__()
# 1
g.__next__()
# 2
g.__next__()
# StopIteration
이터레이터는 __next__ 메서드를 직접 return으로 반환값을 지정한다. 하지만 제너레이터는 yield에 지정한 값이 __next__ 메서드의 반환값으로 나온다.
이터레이터는 raise로 StopIteration 에러를 직접 발생시키지만, 제너레이터는 함수의 끝까지 도달하면 자동으로 에러가 발생한다.
제너레이터는 제너레이터 객체에서 __next__ 메서드를 호출할 때마다 함수 내의 yield까지 코드를 실행하며 yield에서 값을 발생한다.
yield 의 동작 과정
yield는 생산하다 혹은 양보하다 라는 뜻을 가지고 있다.
값을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보한다.
현재 함수 실행을 잠시 중단하고 함수 바깥의 코드가 실행되게 한다.
def number_generator():
yield 0
yield 1
yield 2
g = number_generator()
a = next(g)
b = next(g)
c = next(g)
print(a)
print(b)
print(c)
# 결과값
# 0
# 1
# 2
제너레이터 함수가 실행되는 중간에 next로 값을 가져온다.
def test():
print("A지점")
yield 1
print("B지점")
yield 2
print("C지점")
output = test()
print("D")
a = next(output)
print(a)
print("E")
b = next(output)
print(b)
print("F")
c = next(output)
print(c)
print(output)
# 결과값
# D
# A지점
# 1
# E
# B지점
# 2
# F
# C지점
# StopIteration
# test() 함수의 마지막 부분에 yield 값이 없어서 오류가 없기 때문에 C지점까지 출력한 이후
# c에 들어갈 값이 없어서StopIteration 에러가 발생한다.
제너레이터 객체는 함수의 코드를 실행할 때마다 조금씩 사용한다. 그 이유는 메모리의 효율성을 위해서 작동하기 때문이다.
제너레이터와 return
제너레이터는 함수 끝까지 도달하면 StopIteration 에러가 발생한다.
return 도 함수를 끝내는 메서드기 때문에 return 으로 함수를 종료하면 StopIteration 에러가 발생하게 된다.
def one_generator():
yield 1
return "return 에 지정한 값"
g = one_generator()
print(next(g))
print(next(g))
# 결과값
# 1
# StopIteration: return 에 지정한 값
# 리턴을 만나는 순간 StopIteration이 발생을 한것과 같다.
try:
g = one_generator()
print(next(g))
print(next(g))
print(next(g))
except StopIteration as e:
print(e)
# 결과값
# 1
# return 에 지정한 값
# 2번째에서 이미 exception이 발생하여 이후 코드는 실행하지 않기 때문에 두줄만 값이 나온다.
range 처럼 동작하는 제너레이터 만들기
def number_generator(stop):
n = 0 # 숫자는 0부터 시작
while m < stop: # 현재 숫자가 반복을 끝낼 숫자보다 작을때 반복
yield n # 현재 숫자를 바깥으로 전달
n += 1 # 현재 숫자를 증가시킴
for i in number_generator(3):
print(i)
# 결과값
# 0
# 1
# 2
g = number_generator(3)
next(g)
# 0
next(g)
# 1
next(g)
# 2
next(g)
# StopIteration
def upper_generator(x):
for i in x:
yield i.upper() # 함수의 반환값을 바깥으로 전달
fruits = ["apple", "pear", "grape", "pineapple", "orange"]
for i in upper_generator(fruits):
print(i)
# 결과값
# APPLE
# PEAR
# GRAPE
# PINEAPPLE
# ORANGE
yield 에 무엇을 저장하든 그 결과만 바깥으로 전달을 한다.
yield from 으로 값을 여러번 밖으로 전달하기
# 지금까지 배운 방식
def number_generator():
x = [1, 2, 3]
for i in x:
yield i
for i in number_generator():
print(i)
# 결과값
# 1
# 2
# 3
위의 코드같은 상황에 반복문 대신 yield from을 사용할 수 있다.
yield from 에는 반복 가능한 객체, 이터레이터, 제너레이터 객체를 지정할 수 있다.
표현 방법
yield from 반복 가능 객체
● yield from 은 파이썬 3.3버전 이상부터 사용이 가능하다.
def number_generator():
x = [1, 2, 3]
yield from x # 리스트에 들어있는 요소를 한 개씩 바깥으로 전달
for i in number_generator():
print(i)
# 결과값
# 1
# 2
# 3
g = number_generator()
next(g)
# 1
next(g)
# 2
next(g)
# 3
next(g)
# StopIteration
yield from 에 제너레이터 객체 지정하기
def number_generator(stop):
n = 0
while n < stop:
yield n
n += 1
def three_generator():
yield from number_generator() # 숫자를 세 번 바깥으로 전달
for i in three_generator():
print(i)
# 결과값
# 0
# 1
# 2
제너레이터 표현식
리스트 내포는 처음부터 리스트 요소를 만들어내지만 제너레이터 표현식은 필요할 때 요소를 만들어 내기 때문에 메모리 절약이 가능하다.
표현 방법
(식 for 변수 in 반복 가능한 객체)
# 리스트 내포
[i for i in range(50) if i % 2 == 0]
# 결과값
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48]
# 제너레이터 표현식
(i for i in range(50) if i % 2 == 0)
# 결과값
# <generator object <genexpr> at 0x7fd1925cadc0>