값을 순차적으로 불러올 수 있는 객체.
Iterator를 만들 수 있는 객체를 iterable이라고 부른다.
파이썬에서 자주 사용하는 리스트, 튜플, 집합, 딕셔너리같이(range, enumerate, map, zip같은 함수로 만들어지는 객체들도) for문에 사용할 수 있는 객체는 모두 iterable하다고 보면 된다.
dir 함수를 통해 호출한 객체의 attribute중에서 __iter__ 이 있으면 iterable이다.
iter함수는 객체의 __iter__ 속성을 리턴하는 함수이다.
a = [1,2,3]
b = iter(a)
print(b) # <list_iterator object at 0x1086c70d0>
이렇게 만들어진 iterator 객체 b는 __next__ 속성을 통해 값을 순차적으로 불러올 수 있으며, next 함수는 iter 함수와 마찬가지로 객체의 __next__ 속성을 리턴한다.
print(next(b)) # 1
print(next(b)) # 2
print(next(b)) # 3
print(next(b))
# Traceback (most recent call last):
# File "<pyshell#69>", line 1, in <module>
# print(next(b))
# StopIteration
iterator가 불러올 수 있는 값들을 모두 불러오고 다시 불러오면 StopIteration이 발생한다.
따라서 StopIteration이 발생할 때 함수 실행을 종료한다면, for문을 직접 구현할 수 있다.
a = [1,2,3]
iterator_a = a.__iter__()
I = iter(a)
while True:
try:
n = next(I)
except StopIteration:
break
print(n**2, end=' ') # 1 4 9
Iterator가 컬렉션의 모든 값을 가지고 있는 것과 달리, generator는 yield에 의해 값을 하나씩 호출하는 특성을 가지고 있다. 따라서 generator를 생성하려면 yield 문이 필요하다.
def one_generator():
yield 1
yield 2
return 'return에 지정한 값'
try:
g = one_generator()
print(type(g)) # <class 'generator'>
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 'return에 지정한 값'
except StopIteration as e:
print(e)
이렇게 만들어진 객체 g는 generator가 된다. generator를 next함수로 한번씩 호출할 때마다, 만나는 yield에서 값을 반환하고, 다시 호출하면 그 yield 부터 다음 yield까지의 코드를 실행한다.
generator 객체는 send를 통해 yield 단계에서 대기하고 있는 generator에 원하는 값(혹은 수식)을 전달하는 것도 가능하다!
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() # generator 객체 생성
next(gen) # generator_send 함수의 첫번째 yield까지 실행
print(gen.send(3)) # received value = 3
# 6
# send를 통해 3을 전달하여 received_value = 3 으로 다음 yield까지 실행 후,
# 반환값인 6을 print
next(gen) # while문을 돌아 다시 첫번째 yield까지 실행
print(gen.send(2)) # 위 과정을 반복
list comprehension과 마찬가지로 generator도 comprehension이 가능한데, [] 대괄호가 아닌 () 소괄호를 이용한다.
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() )
# 1
# 4
# 9
List comprehension이 모든 값을 가진 컬렉션(리스트)를 생성하는 것과 달리, generator comprehension은 표현식만을 갖는 generator 객체를 생성한다.
따라서 이렇게 만들어진 generator는 호출할 때마다 위 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)
# comprehension_list=
# sleep 1s
# sleep 1s
# sleep 1s
# 1
# 2
# 3
# generator_exp=
# sleep 1s
# 1
# sleep 1s
# 2
# sleep 1s
# 3
위 실행결과처럼 list comprehension은 리스트의 모든 요소를 생성하기 때문에 리스트 요소 출력을 시작하기 까지 3초가 소요되는 반면, generator comprehension은 그 표현식에 따라 값을 하나씩 반환하기 때문에 1초에 하나씩 값을 출력한다.
Generator Comprehension의 장점은 데이터가 너무 커서 모든 데이터를 리턴할 수 없는 경우, 연산 시간이 너무 오래 걸려서 일부만 처리하는 것이 필요한 경우 등에 사용할 때 유용하다는 것이다.