Iterable, Iterator 그리고 generator

Sawol·2021년 4월 26일
0

I Need Python

목록 보기
3/13
post-thumbnail

파이썬을 조금만 공부하다보면 마주치는 iterable, iterator 그리고 generator까지 제대로 정리해보려고 한다. 분명 이전에도 이터레이블, 이터레이터, 제너레이터에 대해 공부를 했는데 제대로 이해가 된 것이 아닌지, 볼 때마다 어렵다. 그래서 본 글에서 명확하게 정의를 내리고, 왜 쓰는 지, 어디에 쓰는 지 등을 알아 보겠다.

Iterable

__iter__라는 메서드를 구현한 객체를 이터러블 객체라고 한다. 쉽게 말하면 반복 가능한 객체를 의미한다. 여기서 말하는 반복은 한 번에 하나의 요소를 가져오는 것을 말한다. 즉, iterable 객체는 한 번에 하나의 요소를 가져올 수 있다는 의미이다.
그럼 어떻게 해야 iterable 객체의 한 요소를 반복해서 가져올 수 있을까?
바로 iterator를 사용하면 된다.

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

어떤 객체가 Iterable 일까?

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를 사용하면 객체 내부에 어떤 메서드가 존재하는지 확인할 수 있다.

Iterator를 사용하는 이유

객체가 여러 요소 값을 가질 때, 해당 요소들에 순차적으로 접근할 수 있게 해준다. 즉, 이터레이터가 없다면 우리는 list과 같은 배열의 각 요소에 접근할 수 없다.

generator

여기에도 정리를 했었지만, 다시 한번 정의하자면, 제너레이터는 특별한 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

generator를 사용하는 이유

  • 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

위 코드를 보면 제너레이터가 이터레이터에 비해 얼마나 메모리 효율이 좋은지 알 수 있다.

generator를 사용하여 reversed() 함수 구현하기

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된 새로운 리스트를 반환하는것이 아니라 원래 리스트에 인덱스 값을 이용해 값을 리턴하므로 메모리가 낭비되지 않는다.

0개의 댓글