[TIL - 8 / Python] generator

haejun-kim·2020년 7월 23일
0

[Python]

목록 보기
15/19
post-custom-banner

Generator와 yield

Generator

이터레이터를 생성해주는 함수

이터레이터는 클래스에 __iter__, __next__ 또는 __getitem__ 메소드를 구현해야하지만 제너레이터는 함수 안에서 yield라는 키워드만 사용하면 된다. 따라서 제너레이터를 사용하면 이터레이터보다 훨씬 간단하게 작성할 수 있다.
함수 안에서 yield를 사용하면 함수는 제너레이터가 되며 yield에는 값(변수)을 지정한다.

yield

  • yield 값

제너레이터는 제너레이터 객체에서 __next__ 메소드를 호출할 때마다 함수 안의 yield까지 코드를 실행하며 yield에서 값을 발생시킨다. 그래서 제너레이터라고 부른다.


(※ 제너레이터 함수 호출 > 제너레이터 객체 > __iter__는 self 반환 > 제너레이터 객체)

yield : 생산하다, 양보하다
즉, yield를 사용하면 값을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보한다. 따라서, yield현재 함수를 잠시 중단하고 함수 바깥의 코드가 실행되도록 만든다. (return은 반환 즉시 함수가 종료됨)
yield를 사용하여 바깥으로 전달한 값은 next 함수(__next__)의 반환값으로 나온다.

send

제너레이터 함수는 실행중에 send함수를 통해서 값을 전달할수 있다.

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 실행 루프에서 연산결과에 따라 호출도 제어할 수 있다.

Generator와 return

제너레이터는 함수 끝까지 도달하면 StopIteration 예외가 발생한다. 마찬가지로 return도 함수를 끝내므로 return을 사용해서 함수 중간에 빠져나오면 StopIteration예외가 발생한다. 특히, 제너레이터 안에서 return에 반환값을 지정하면 StopIteration 예외의 에러 메시지로 들어간다.

제너레이터 표현식

제너레이터 함수외에도 제너레이터 표현식(Generator expression)이 있는데, 제너레이터 표현식은 지연 실행(Lazy evaluation)을 위해서 사용될 수 있다.

제너레이터 표현식의 문법은 list comprehension문법과 비슷하지만 []가 아닌 ()를 사용하여 만든다. 다음 코드는 generator expression을 사용해서 제곱연산을 하는 예제다.

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() )
  • 실행 결과

<generator object generate_square_from_list.<locals>.<genexpr> at 0x7ff6af079ac0>

1
4
9


Assignment

다음 코드를 실행해보고 분석한 결과를 적어보자. (lazy evaluation이란 무엇인지, 장점 및 list comprehension과의 차이점에 대해서도 서술)

import time
L = [1,2,3]
def print_iter(iter):
    for element in iter:
        print(element)

def lazy_return(num):
    print("sleep 1s")
    time.sleep(1)
    return num

print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ]
print_iter(comprehension_list)

print("generator_exp=")
generator_exp = ( lazy_return(i) for i in L )
print_iter(generator_exp)
  • 실행 결과 - 1. list comprehension
  • 실행 결과 - 2. generator expression

lazy evalutaion

어떤 값이 실제로 쓰일 때 까지 그 값의 계산을 뒤로 미루는 동작 방식

실제로 값을 사용될 때만 사용하기 때문에 시간과 메모리 절약할 수 있다(미리 연산해놓지 않고 미리 저장해놓지 않기 때문에)는 장점이 있다.

generator expression vs list comprehension

위의 코드를 토대로 generator expression과 list comprehension을 비교해보자.

  • list comprehension

    • []로 묶는다.

    • 집에 열명이 올지 말지 정확히 모르는 상태에서 일단 라면을 열봉지를 끓여놓고 기다린다. 어쩌면 라면을 다 버리게 될 수 도 있다. 이게 list comprehension이다. 뒷일을 생각하지 않고 우선 연산한다.

    • 실제 print를 하기 전부터 실제 사용 될지에 대한 판단도 없이 우선 lazy_return()의 기능을 수행한다. time.sleep(1)가 먼저 세번 수행이 되고 나서print(element)가 출력된 것을 보아 알 수 있다.

  • generator expression

    • ()로 묶는다.

    • 집에 열명이 올지 안올지 모르는 상태이니 먼저 라면을 다 끓여놓지 말고 몇명이 올지 확정이 되면 바로 라면을 끓일 수 있도록 끓일 준비만 다 해놓는다. 이게 generator expression이다.

    • 실제로 lazy_return()함수가 우선 값을 미리 만들어놓는 것이 아니라 값을 사용하는 순간에만 함수를 수행하고 있음을 확인할 수 있다. 그렇기 때문에 출력 결과에서 time.sleep(1)print(element)번갈아가면서 출력되고 있다.

따라서

값이 실제로 사용되지 않으면 연산 또한 하지 않으므로 시간과 메모리를 절약할 수 있다. 하지만 그렇다고 무조건적으로 generator expression이 옳다는것은 아니다. 모든 요소나 대부분의 요소가 사용될 것이 확실한 상황이라면 list를 통해 미리 연산을 해두는 것이 더 효율적이니 상황에 맞게 사용할 수 있도록 하자.


📕 참고한 블로그를 읽고 이해가 많이 됐다.😂

post-custom-banner

0개의 댓글