값을 순차적으로 꺼낼 수 있는 객체를 뜻한다.
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
위에서 배운 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에 넘겨주는 것이다.
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)