[Python] 5_Generator

dev.soo·2020년 8월 26일
0

Python

목록 보기
4/6
post-thumbnail

아래 페이지를 참고하여 Generator 에 대해 알아보고 List comprehension 와 비교해보겠다.

Generator

generator functions are a special kind of function that return a lazy iterator. These are objects that you can loop over like a list. However, unlike lists, lazy iterators do not store their contents in memory.

Generator expression (표현문) 은 List Comprehension 과 비슷하지만 소괄호() 를 사용한다.

다음 예시를 살펴보자.

# Demonstrate Python Generator Expression

# Define the list
alist = [4, 16, 64, 256]

# Find square root using the list comprehension
out = [a**(1/2) for a in alist]
print(out)

# Use generator expression to calculate the square root
out = (a**(1/2) for a in alist)
print(out)
print(next(out))
print(next(out))
print(next(out))
print(next(out))
print(next(out))

LC 과 Generator 의 차이점은 아래와 같다.
1. 표현문에서 LC 는 [ ] 사용, generator 는 ( ) 사용
2. LC 는 full list 를, generator 는 실행을 지연시켜 한 번에 하나의 값만 return 한다.

즉, list는 full sequence 를 메모리에 저장하게 되어 메모리 측면에서 효율성이 떨어진다. 반면 generator는 lazy evaluation 을 통해 한 번에 하나의 item 을 프로세스 하기 때문에 메모리 효율이 높다.

이를 고려하고 결과를 살펴보면,

[2.00, 4.0, 8.00, 16.0] # 모든 element 가 하나의 list 에 담겨 return 됨
 at 0x000000000359E308>
2.0 # iterator 가 되어  next() 함수로 값을 하나씩 호출함 : generator 에서 가장 
4.0 # 자주 사용되는 method
8.0
16.0
Traceback (most recent call last):
  File "C:/Python/Python35/python_generator.py", line 17, in 
    print(next(out))
StopIteration 

generator 표현문 외에 generator 는 yield 를 사용하여 함수로 표현할 수 있다. iterator 를 작성할 때, class 안에서 __iter__, __next 등을 사용했지만, generator 는 함수 안에서 yield 라는 키워드만 사용하여 generator function 으로 만든다.

alist = ['Python', 'Java', 'C', 'C++', 'CSharp']
def list_items():
    for item in alist:
        yield item

gen = list_items()

iter = 0

while iter < len(alist):  
    print(next(gen))
    iter += 1

while 문에서, yield 를 사용해서 item을 next() 함수로 호출하기 전까지 item 을 산출(yield) 하였다. yield 는 제너레이터 함수에서 값을 반환할 때 사용되며, yield 호출 후에 next가 호출될 때까지 현재의 상태에서 머물고 있다가 next 함수가 호출되면 이어서 연산을 수행한다.


for loop 를 사용하면, next() 함수가 내포적으로 호출되어 값을 하나씩 호출한다.

alist = ['Python', 'Java', 'C', 'C++', 'CSharp']
def list_items():
    for item in alist:
        yield item

gen = list_items()

for item in gen:
    print(item)
    
# 결과 : 
# Python
# Java
# C
# C++
# CSharp

Generator function 과 일반적인 function 을 비교해 보면,

  1. generator function 은 yield 를, 일반적인 function 은 return 을 호출한다.
  2. generator function 은 한 번 혹은 여러 번의 yield 호출을 갖는다.
  3. return statement 가 마지막으로 실행되는 것이라면, yield 호출은 실행을 일시 정지하고 iterator 을 반환한다.
  4. next() method 는 generator function 을 실행시킨다.
  5. 지역변수는 연속적인 next() method 호출 사이사이에 보관된다.

Send

generator function 은 실행 중에 send method를 사용할 수 있다.
send() method 는 generator 가 yield 한 값에 send 함수 내부의 값을 전달 하는 것이다.

1 def double_inputs():
2    while True:
3        x = yield
4        yield x * 2
5
6 gen = double_inputs()
7
8 next(gen)       
9 print(gen.send(10))    

line 8의 next(gen)을 실행하면 line 3에서 필요한 yield 값을 전달받기를 기다렸다가 line 9에서 send(10)을 통해 10을 yield 대신 넣어주었다. next(gen) 을 작성하지 않으면 다음과 같은 에러가 난다.

def double_inputs():
    while True:
        x = yield
        yield x * 2

gen = double_inputs()

# print(next(gen))       
print(gen.send(10))    

결과 :
    print(gen.send(10))    
TypeError: can't send non-None value to a just-started generator

generator 가 지금 막 시작되어 send 값을 전달할 수 없다고 한다. 그래서 next() 함수를 통해 generator 를 시작해주고 yield 가 나오는 부분에서 멈췄다가 (lazy evaluation) 추후에 send special method 를 통해 yield 값이 전달되면 이어서 실행된다.

그럼 공부한 내용을 바탕으로 아래 코드를 실행하고 분석해 보자.

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)

위의 코드를 통해 time.sleep()처럼 인위적으로 수행 시간이 긴 함수를 만들어 list comprehension 과 generator 의 객체들이 어떻게 실행되고 호출되는지 살펴볼 수 있다.

list comprehension 을 사용하면 list comprehension 의 객체가[lazy_return(1), lazy_return(2), lazy_return(3)]처럼 된다. 그 결과는 아래와 같다.

sleep 1s
sleep 1s
sleep 1s
1
2
3

반면 generator expression 을 사용하면 (lazy_return(1), lazy_return(2), lazy_return(3)) 처럼 괄호를 사용하고 아래와 같은 결과가 나온다. 위에서 언급된 lazy evaluation 을 눈으로 확인 할 수 있다.

sleep 1s
1
sleep 1s
2
sleep 1s
3

0개의 댓글