Python Iterators & Generators

minch·2021년 7월 9일
0

Python

목록 보기
9/13
post-thumbnail

Iterators?

값을 순차적으로 꺼낼 수 있는 객체를 뜻한다.
Iterable(반복 가능한 객체)한 객체이며 이를 함수 또는 메소드로 생성이 가능하다.

Iterable한 객체는 모두 __iter__라는 메소드를 가지고 있어
dir() 함수를 통해 쉽게 확인 할 수 있다.

>>> list = [ 1, 2, 3]
>>> dir(list)
['__add__', '__class__', '__class_getitem__', '__contains__',
'__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__gt__', '__hash__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmul__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', 'count', 'index']

이러한 iterable한 객체를 iterator로 바꾸기 위해서
__iter__ 메소드를 사용하면 된다.

>>> L = list.__iter__()

그렇게 되면, 이 iterator를 __next__ 메소드를 통해 다음 element를 불러 올 수 있다.

>>> L.__next__()
1
>>> L.__next__()
2
>>> L.__next__()
3
>>> L.__next__()
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    L.__next__()
StopIteration

StopIteration 에러는 index 값이 벗어나면 발생한다.
이를 이용하여 iterator를 출력할 수 있다.

list = [1, 2, 3]
L = list.__iter__()
while True:
    try:
        X = next(L)
    except StopIteration:
        break
    print( X**2, end=" ")

출력

1 4 9 

Generators

위에서 배운 Iterator를 쉽게 생성하는 방법이 있다.

그것이 바로 Generator!

일반적인 보통의 함수는 값을 반환(return)하고 종료 하지만 Generators 함수는 값을 반환하기는 하지만 산출(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>

위에서 확인 할 수 있듯이, generator_squares()generator 객체라는 것을 알 수 있다.

여기서 yield란?
generator 함수에서 값을 반환할 때 사용되며 yield 호출후에 다시 next가 호출될때 까지 현재의 상태에서 머물고 있다가 next 함수가 호출되면 이전의 상태에 이어서 다음 연산을 수행한다.

그리고, 이 제너레이터는 __iter__, __next__ 메소드 두개 다 가지고 있다.

또 제너레이터 함수는 실행 중에 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

여기서 yield가 send통해 받은 값을 received_value에 넘겨주는 것이다.

Generator Expression

Comprehension과 비슷하지만 한번에 하나의 요소만 반환하는 Generator를 생성한다는 차이점이 존재한다.

gen = (x**2 for x in range(10))
print(gen)
print(next(gen)) # call 1
print(next(gen)) # call 2
...
print(next(gen)) # call 9
print(next(gen)) # call 10

출력

<generator object <genexpr> at 0x7f96f0eed820>
0 # call 1
1 # call 2
...
64 # call 9
81 # call 10

언뜻 보면 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)

출력

위처럼 서로 다른 출력을 볼 수 있다.

그렇다면 왜 이런 출력을 보일까?

list comprehension을 이용한 함수는 그 크기만큼 미리 데이터에 공간을 할당하고 저장하기 때문이다.
반면, Generator Expression 함수인 generator_exp는 마찬가지로 공간을 생성하긴 하지만 아직 그 값이 정해져 있지 않고, 반환 규칙과 현재 상태만을 저장해 놓는다.

이를 이용하는 것이 바로 lazy evaluation이다.

lazy evaluation(느긋한 계산법)은 불필요한 계산을 피하고, 원하는 값만 받을 수 있다는 장점이 있다.

참조
(https://wikidocs.net/16068)
(https://itholic.github.io/python-lazy-evaluation/)
(https://dojang.io/mod/page/view.php?id=2412)

0개의 댓글