[Python] iterator, generator

Kook Han·2020년 6월 1일
0

Python

목록 보기
10/13

1. iterator

iterator는 값을 순차적으로 꺼내올 수 있는 객체이다. 아래와 같이 L 이라는 리스트가 있을 때, 리스트의 요소를 순차적으로 꺼내준다.

>>> L=[1,2,3]
>>> iter_L=L.__iter__()
>>> print(iter_L.__next__())
1
>>> print(iter_L.__next__())
2
>>> print(iter_L.__next__())
3

iterator는 해당 리스트를 dir 했을때 iter 함수가 들어있으면 사용 가능하다.

맨 위의 예제처럼 iterator를 변수에 저장하고 next 함수를 호출하면 for 구문이 한줄 한줄 실행되는 것처럼 값을 하나씩 꺼내올 수 있다.

2. generator

generator는 iterator를 생성해주는 함수라고 볼 수 있다. 단, generator는 값을 반환하기 보다는 산출(yield)한다는 차이점이 있다.

또한 함수 자체에 iternext가 포함되어 있어 아래와 같이 iter를 먼저 호출하지 않고 next를 바로 호출할 수 있다.

>>> def generator_squares():
    for i in range(3):
        yield i ** 2

        
>>> gen = generator_squares()
>>> print(gen.__next__())
0
>>> print(gen.__next__())
1
>>> print(gen.__next__())
4

generator의 또다른 특징은 실행 중에 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

위의 코드 중간에 나오는 gen.send()의 값을 위 함수의 yield인 received_value에 할당하여 최종 값이 산출되는 것을 확인할 수 있다.

3. lazy evaluation

lazy evaluation은 쉽게 설명하자면 어떤 문장을 실제 실행하기 전에 사전에 준비만 해놓는 것으로 볼 수 있다.

예를 들어 음식점에서 최종 인원이 미정인 채 어떤 팀의 예약을 받았을 때, 몇명이 올지 모르는 상태에서 만들 수 있는 음식의 최대치를 미리 만들어놓고 예약 팀이 도착했을때 음식을 제공하는 것을 생각해보자.

만약 예약 인원이 최대치에 근접하게 음식점에 도착한다면 미리 음식을 준비한 선택은 매우 효율적인 선택이 될 것이지만 만약 실제로 온 인원이 준비한 음식의 양에 크게 못미칠 경우 매우 비효율적인 선택이 될 것이다.

따라서 음식 재료와 레시피만 미리 준비해놓고, 실제 예약 팀이 도착했을때 해당 인원에 맞게 음식을 만들고 제공하는 것이 가장 안전하고 효율적인 선택이 될 것이다.

이것이 lazy evaluation의 개념이다. lazy evaluation은 generator에 활용된다.

아래 예제를 살펴보자

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)

comprehension list=
sleep 1s
sleep 1s
sleep 1s
1
2
3

list 컴프리헨션으로 실행한 comprehension_list = [ lazy_return(i) for i in L ] 구문은 lazy_return()가 정의된 것처럼 sleep 1s가 L의 갯수 만큼인 세번 먼저 등장하게 된다.

이후 print_iter(comprehension_list) 구문이 실행되게 되어 결과가 표시된다.

이번에는 generator로 값을 구해보자.

>>> print("generator_exp=")
>>> generator_exp=(lazy_return(i) for i in L)
>>> print_iter(generator_exp)

generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3

sleep 1s가 한번 출력될 때마다 print_iter(generator_exp)도 실행된다.

generator로 실행 시에는 미리 sleep 1s를 전부 다 만들어 놓지 않고 각각 한번씩만 실행된 것을 확인할 수 있을 것이다.

사실 위의 코드는 두개 다 리스트에 있는 요소의 갯수만큼 값이 도출되게 되어있으므로 결론적으로 출력된 값의 줄 수는 똑같지만, 전체 값 중에 소수의 값만 필요할 때에는 generator를 사용하는 것이 부하도 적고 처리 시간도 획기적으로 적을 것이다.

하지만 리스트의 요소를 전부 활용할 것이 확실하다면, list 컴프리헨션이 더 효율적일 수 있으니 상황에 따라 두개의 기능을 적절히 활용하도록 하자.

0개의 댓글