파이썬에서는 보통의 함수는 값을 반환하고 종료
하지만 제너레이터 하수는 값을 반환하기는 하지만 산출(yield) 한다는 차이점이 있다.
제너레이터 = 이터레이터를 생성 해주는 함수
def generator_squares():
for i in range(3):
yield i ** 2
print("gen object=", end=""), print(generator_squares())
gen object=<generator object generator_squares at 0x10f3b0150>
여기서 주목하는 키워드는 yield
yield = 제너레이터 함수에서 값을 반환할 때 사용되며 yield 호출후에 다시 next가 호출될때까지 현재 상태에서 이어서 다음 연산을 수행
def generator_squares():
for i in range(3):
yield i ** 2
print("gen object=", end=""), print(generator_squares())
gen object=<generator object generator_squares at 0x10f3b0150>
제너레이터를 dir로 함수 종류를 확인해보면 이터레이터와 다르게ㅔ iter , next 함수가 둘다 존재하는 것을 확인 가능
그래서 이터에이터에서 처럼 iter 를 호출한 후 next 함수를 호출하지 않아도 바로 next 를 호출 가능
gen = generator_squares()
print(gen.__next__())
print(gen.__next__())
print(gen.__next__())
print(gen.__next__())
출력결과
0
1
4
Traceback (most recent call last):
File "generator.py", line 14, in <module>
print(gen.__next__())
StopIteration
이터레이터와 마찬가지로 next 함수로 값을 꺼내올 수 있고 반복문이 끝나게 되면 Stopiteration 발생
def generator_send():
received_value = 0
while True:
received_value = yield
print("received_value = ",end=""), print(received_value)
yield received_value * 2
gen = generator_send()
next(gen)
print(gen.send(2))
next(gen)
print(gen.send(3))
출력결과
received_value = 2
4
received_value = 3
6
generator_send 함수는 yield로 send 를 통해서 받은 값을 received_value에 할당하고 그 값의 2배 수를 리턴 받고있다. 제네레이터는 yield를 이용해서 제네레이터 함수 실행 중 값을 전달 할 수 있고, 응용하면 제너레이터 함수를 사용해서 main 실행 루프에서 연산결과에 따라 호출도 제어할 수있다.
제너레이터 => Lazy evaluation을 위해 사용 될 수 있다.
*Lazy evalution은 실행을 지연시킨다는 의미
L = [ 1,2,3]
def generate_square_from_list():
result = ( x*x for x in L )
print(result)
return result
def print_iter(iter):
for element in iter:
print(element)
print_iter( generate_square_from_list() )
제네레이터 표현식의 문법은 리스트 컴프리헨션 문법과 비슷하지만 대괄호( [] )가 아닌 괄호() 사용하여 만듬
만약 당신이 스터디룸에서 일하는 직원이다.
이 스터디룸은 스터디 공간뿐 아니라, 간단한 간식도 제공한다.
오후 7시에 10명이 스터디룸을 예약하고, 간식으로 컵라면을 주문해놓았다.
하지만 예약한 손님이 말하길,
“다들 바빠서 몇 명이 참석할지 모르겠네요, 최악의 경우 아무도 못갈수도 있어요 ㅠㅠ”
당신이 직원이라면 컵라면 10개를 미리 다 끓여 놓겠는가?
아니면 언제든 최대 10명까지 제공할 수 있는 상태로 준비만 해 두겠는가?
확실히 10명이 제시간에 온다는 보장이 있다면 전부 미리 물을 부어놓는게 효율적이겠지만,
이 상황은 당연히 후자가 효율적이다.
후자의 경우가 바로 Lazy Evaluation이다. (출처 : https://itholic.github.io/python-lazy-evaluation/)
lazy evaluation = 어떤 값이 실제로 쓰일 때 가지 그 값의 계산을 뒤로 미루는 동작 방식
예시)
숫자1을 반환하는 단순한 함수가 있다.
반환하기 전에 "return 1"이라는 문자열을 출력
def return_one():
print("return 1")
return 1
return_one을 10번 수행해서, 숫자 1을 10개를 담는 리스트를 만들면
def return_one():
print("return 1")
return 1
print("[let's make one_list !]")
one_list = [return_one() for x in range(10)]
리스트에서 값을 하나씩 출력
def return_one():
print("return 1")
return 1
print("[let's make one_list !]")
one_list = [return_one() for x in range(10)]
print("[let's print one_list !]")
for one in one_list:
print(one)
출력 결과
[let's make one_list !]
return 1
return 1
return 1
return 1
return 1
return 1
return 1
return 1
return 1
return 1
[let's print one_list !]
1
1
1
1
1
1
1
1
1
1
결론 : one_list를 출력하기 전에 미리 함수 10번이 실행되러 값을 다 만들어 리스트에 저장
이번 차례는 리스트 대신 generator로 값 생성
list comprehension에서 대괄호만 소괄호로 바꾸어주면 generator expression이 됨
코드 예시)
def return_one():
print("return 1")
return 1
print("[let's make one_generator !]")
one_generator = (return_one() for x in range(10)) # 대괄호[] 를 소괄호() 로 바꿈
print("[let's print one_generator !]")
for one in one_generator:
print(one)
실행 결과
[let's make one_generator !]
[let's print one_generator !]
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
return 1
1
let's make one_generator 아래쪽에는 아무것도 없다.
즉 실제로 값을 출력하기 전에는 retuurn_one 함수가 한번도 출력되지 않는다
값이 실제로 사용되지 않으면 연산 또한 하지 않으므로 시간과 메모리를 절약 할수있다.
필요할때만 평가되기 때문에 메모리를 효율적으로 사용 가능
무한 자료구조를 만들어 사용 가능
실행도중의 오류상태를 피할수있음
컴파일러 최적화 가능
Lazy evaluation 같은 경우 제너레이터 객체를 사용하여 연산이 필요할 때 next 함수를 호출하여 그 즉시 메모리에 올려서 효율적으로 메모리를 사용할 수 있는 장점이 있고 list comprehension의 경우는 리스트를 만드는 즉시 값을 전부 다 메모리 위에 올려 놓기 때문에 메모리의 효율적으로 사용할 수 없다.