파이썬을 조금만 공부하다보면 마주치는 iterable
, iterator
그리고 generator
까지 제대로 정리해보려고 한다. 분명 이전에도 이터레이블, 이터레이터, 제너레이터에 대해 공부를 했는데 제대로 이해가 된 것이 아닌지, 볼 때마다 어렵다. 그래서 본 글에서 명확하게 정의를 내리고, 왜 쓰는 지, 어디에 쓰는 지 등을 알아 보겠다.
__iter__
라는 메서드를 구현한 객체를 이터러블 객체라고 한다. 쉽게 말하면 반복 가능한 객체를 의미한다. 여기서 말하는 반복은 한 번에 하나의 요소를 가져오는 것을 말한다. 즉, iterable
객체는 한 번에 하나의 요소를 가져올 수 있다는 의미이다.
그럼 어떻게 해야 iterable
객체의 한 요소를 반복해서 가져올 수 있을까?
바로 iterator
를 사용하면 된다.
iterable
객체에 iter()
함수를 호출하면 iterator
가 된다. iterator
가 되면 next()
또는 __next__
메서드를 사용할 수 있으며, 다음 요소에 접근이 가능하다. 만약, 모든 요소를 반환했으면 stopIteration
에러가 발생한다.
또한 iterable
객체를 iterator
로 바꾸지 않고, 내장함수 next()
를 사용하면 이 객체는 iterator
가 아니라는 오류가 발생한다.
iterator
는 모든 동작을 완료한 후 결과를 한꺼번에 메모리에 적재시킨다.
우리가 for
문을 사용할 때는 iterable
객체를 그대로 사용하는데, 그 이유는 for
문의 내부에서 자체적으로 iterable
객체를 iterator
로 만들어주기 때문이다. 즉, 아래와 같이 for
문을 실행하면 자동으로 iter(obj)
을 실행해 iterator
를 생성한다.
< for 문의 내부 코드 >
# create an iterator object from that iterable
iter_obj = iter(iterable)
# infinite loop
while True:
try:
# get the next item
element = next(iter_obj)
# do something with element
except StopIteration:
# if StopIteration is raised, break from loop
break
for
문으로 요소를 반환할 수 있는 경우 iterable
객체이다. 이를 좀 더 정확하게 표현하자면, 객체 내부 메서드에 __iter__
가 있으면 iterable
한 객체이다. 또한 여기서 __next__
메서드까지 존재하면 iterator
이다.
test = 'abcdefg'
print(dir(test))
# ['__add__', '__class__', '__dir__', '__doc__', '__iter__', .....]
print(dir(iter(test)))
# [ '__iter__', '__le__', '__ne__', '__new__', '__next__', ...]
dir
를 사용하면 객체 내부에 어떤 메서드가 존재하는지 확인할 수 있다.
객체가 여러 요소 값을 가질 때, 해당 요소들에 순차적으로 접근할 수 있게 해준다. 즉, 이터레이터가 없다면 우리는 list
과 같은 배열의 각 요소에 접근할 수 없다.
여기에도 정리를 했었지만, 다시 한번 정의하자면, 제너레이터는 특별한 iterator
이다. 즉, 모든 제너레이터는 이터레이터이다. 다만, 앞서 봤던 이터레이터의 특징인 __iter__
과 __next__
메서드 사용을 피하고 yield
를 사용하여 조금 더 간결하고 파이썬스러운 문법을 제공한다.
제너레이터를 생성하는 방법은 리스트 컴프리헨션과 비슷하다. 단지, []
를 ()
로 바꿔주면 된다.
제너레이터는 일회용 함수로 한번 사용한 후라면 다시 호출하여도 아무런 결과가 반환되지않는다.
def fuc():
yield "hi"
yield "I AM generator"
yield "generator is iterator"
yield "but iterator is not generator"
generator = fuc()
print(generator) # <generator object fuc at 0x7fed65d78f90>
print(next(generator)) # hi
for i in generator:
print("first", i) # I AM generator/generator is iterator/but iterator is not generator
for i in generator:
print("second", i) # 결과 반환 X
iterator
의 __next__
, __iter__
보다 우아하고 간결한 파이써닉한 방법이다.import sys
print(sys.getsizeof([i for i in range(1000)])) # iterator : 9016
print(sys.getsizeof((i for i in range(1000)))) # generator : 112
위 코드를 보면 제너레이터가 이터레이터에 비해 얼마나 메모리 효율이 좋은지 알 수 있다.
def make_reversed(a:list):
for i in range(len(a)):
yield a[-i-1]
generator = make_reversed([1,2,3,4,5])
print(generator) # <generator object make_reversed at 0x7f94ec765f90>
for j in generator:
print(j) # 5,4,3,2,1
reverse
된 새로운 리스트를 반환하는것이 아니라 원래 리스트에 인덱스 값을 이용해 값을 리턴하므로 메모리가 낭비되지 않는다.