TIL[56]. Python_generator

jake.log·2020년 8월 24일
0

Python

목록 보기
38/39

38.generator

제너레이터 함수 : 값을 반환하기는 하지만 산출(yield)한다.

그리고 제너레이터는 '이터레이터를 생성해주는 함수'라고도 볼 수 있다.

다음 코드는 함수안에서 yield를 사용하여 리스트의 제곱을 산출하는 함수가 있고,
이 함수를 print문으로 확인해보면 generator object 임을 확인할 수 있다.

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는 제너레이터 함수에서 값을 반환하고,
  • 다시 next가 호출될때 까지 현재의 상태에서 머물고 있다가
  • next 함수가 호출되면 이전의 상태에 이어서 다음 연산을 수행한다.

제너레이터를 dir로 함수 종류를 확인해보면 이터레이터와는 다르게 __iter__ 와 __next__ 함수가 둘다 들어있는 것을 확인할 수 있다.

print("dir gen =", end=""), print(dir(generator_squares()))

dir gen =['__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']

그래서 이터레이터에서 처럼 __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__ 함수로 값을 꺼내올 수 있고
for문이 끝나게 되면 StopIteration이 발생한다.

그리고 제너레이터 함수는 실행중에 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 expression)이 있다.
제너레이터 표현식은 실행 지연(Lazy evaluation)을 위해서 사용될 수 있다.

제너레이터 표현식의 문법은 리스트 컴프리헨션 문법과 비슷하지만 대괄호( [ ])가 아닌 괄호를 ( ) 사용하여 만든다.

예제)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() )
output > 1,4,9

이번 과제는 다음코드를 실행해보고 분석한 결과를 블로깅하는 과제 입니다. lazy evaluation 이란 무엇인지와 장점 및 리스트 컴프리헨션과의 차이점에 대하여 블로깅 해주세요.

L = [ 1,2,3]

import time

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)

output >

위의 코드를 보면 comprehension_list는 [] 대괄호로 쌓여 있고, generator_exp는 ()중괄호로 쌓여있다.

comprehension_list는 리스트기 때문에 위 코드처럼 리스트 컴프리헨션(lazy_return(i))을 먼저 진행해주고나서 아래 print를 출력해준다. 즉, 리스트 컴프리헨션은 lazy_return(num) 함수를 모두 처리한 후 print_iter(iter) 함수를 실행한다.

그러나 generator_exp의 경우 일반적인 반복문과는 연산하는 절차가 다르다.

제너레이터는 값을 반환할 때 연산을 잠시 멈추고 외부로 값을 보낸다.
따라서 lazy_return(num) 함수에서 return한 값을 갖고
print_iter(iter) 함수를 처리해준다.

  • lazy evaluation이란?

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

  • lazy evaluation의 장점
    리스트의 경우 내부 데이터가 크면 한정된 용량의 메모리는 감당할 수 없다.
    그러나 제너레이터 표현식(generator expression)을 통한 Lazy evaluaiotn은 데이터를
    메모리에 로딩하지 않고, 필요할 때 로딩, 평가를함으로 메모리를 절약 할 수 있다.

  • 리스트 컴프리헨션과의 차이점

  1. 리스트 컴프리헨션과 Lazy evaluation의 차이의 기준은 컴퓨터 메모리이다.
  2. 리스트 컴프리헨션은 코드를 입력하면 메모리의 배열 크키에 비례하는 공간이 할당 된다.
  3. 제너레이터 표현식(generator expression)을 통한 Lazy evaluaiotn은 규칙대로 값을 반환하기에 관리할 여러 상태값을 갖고 있지만, generator를 생성할 때 코드가 할당되지 않는다.

[참고: https://velog.io/@junnoli/Iterator-Generator]

profile
꾸준히!

0개의 댓글