[파이썬] iterable과 iterator 의 차이가 뭐예요?

Clueless Coder·2022년 3월 11일
4

뭐예요?

목록 보기
1/4
post-thumbnail

"뭐에요? 🤨" 시리즈 1편
평소에 헷갈렸던 것들이나 암기해두면 좋을 것들, 대강 알고 정확한 정의를 몰랐던 것들을 좀 깊게 들어가보면서 질문별, 문제상황별로 포스트를 작성해보겠다. 파이썬에 대해서는 기초적인 문법을 알고 있다고 생각하고 작성한다.

iterable

이터러블이 뭐예요?

  • iterate over 가능한 오브젝트(객체)를 말한다. 여기서 iterate over 가능하다는 것은 멤버들을 한 번에 하나씩 돌려줄 수 있는 객체라는 소리다. (멤버를 순회 가능한 객체라고도 한다)
  • 좀 더 정확히 말하자만 이터러블은 순회를 "당한다"라고 이해하는 것이 좋겠다. 뭔소리냐면 이터러블은 어떤 원소가 접근되고 있는지 모른다, 즉 이터러블이 현재 접근되어지는 원소의 인덱스가 무엇인지 추적하고 있지 않다는 것이다.
  • 즉, 이터러블은 순회 과정을 주관하지 않는다. 이터러블은 순회를 당하는 것이 가능한 컬렉션이다.
  • 그럼 누가 이 순회를 주관하는가? 바로 이터레이터다!
  • 이터레이터는 일단 '순회를 주관해주는 애'라고 생각하자. 구체적인 동작 방식에 대해서는 일단 미뤄두고 이터러블에는 뭐가 있는지, 이 순회 당할 수 있는 애들을 어디에 써먹을 수 있는지 보자.

어떤 애들이 이터러블인데요?

기본적으로 __iter__() 혹은 __getitem__() 스페셜 메서드가 있는 클래스 객체들은 다 이터러블이다. (그 말인즉슨 우리가 만든 커스텀 클래스에 저 스페셜 메서드 하나 이상을 구현해주면 이터러블이 된다는 거다.)

다음과 같은 애들이 파이썬에서 기본적으로 사용할 수 있는 이터러블이다. (for .. in [이_자리에_들어갈_수_있는_애들] 로 생각하면 외우기 쉽다)

  • list (mutable sequence type)
  • tuple (immutable sequence type)
  • str (immutable, text sequence type)
  • dict (mutable, non-sequence, mapping type)
  • set (mutable, non-sequence type)
  • file

이터러블 어디다가 쓰나요?

iterable 이라고 쓴 자리에 이터러블을 넣어서 사용한다.

  • for <variable> in <iterable>
  • 시퀀스를 필요로 하는 많은 함수들 (zip(*iterables, strict=False), map(function, iterable, ...), ... )
  • iter(iterable) → 해당 객체의 이터레이터 리턴. 이 이터레이터는 값들을 쭉 한 번씩 거치는 동안 유효하다.
  • 이터러블을 사용할 때, 보통은 이터레이터 객체를 직접 다룰 필요는 없다. for 문의 경우 루프를 도는 동안 이터레이터를 자동으로 생성해서(자동으로 __iter__()를 호출해준다는 뜻) 이름 없는 변수에 잡아둔다.

iterator

이터레이터는 뭐예요?

  • 이터레이터는 이터러블의 순회를 주관하는 애!라고 했다.
  • 파이썬 오피셜 문서에서는 이터레이터를 데이터의 스트림을 표현하는 객체라고 정의한다. (이 정의만 봐서는 사실 감이 잘 안잡힌다. 이터러블과의 차이도 모르겠다.) 그래서 우리 머릿속에는 이터레이터이터러블 순회 주관자라고 정의해보자.

이터레이터가 순회를 어떻게 주관하는데요?

  • 이터러블 오브젝트의 스페셜 메서드(__iter__())가 호출되면 해당 이터러블에 대한 이터레이터 객체가 생성되고 리턴된다.

    • 이터러블의 이 스페셜 메서드가 호출될 때마다 새로운 이터레이터가 리턴되고, 이렇게 이터러블로부터 새로 생성된 이터레이터는 해당 이터러블의 맨 첫 원소를 가르킨다.
  • 이터레이터에는 또 __next__() 라는 스페셜 메서드가 있어서, 이 메서드를 호출하면

    • 현재 접근한 원소를 리턴하고, 현재 인덱스 포인터가 이터러블의 다음 원소를 가르키도록 옮긴다.
    • 모든 원소를 순회하고 나면 다 끝났다고 알려준다.
  • 따라서 이터레이터 형은 두 개의 메서드를 사용해서 구현된다. (이터러블은 __iter__()__getitem__()이 정의되어 있다면) 이터러블 형은

    • __iter__()
    • __next__()

이 두 개의 메서드를 모두 지원해야 한다. (이터레이터 형에도 __iter__() 메서드가 있다는 것은 이터레이터에 대한 이터레이터도 만들 수 있다는 것이다. 또 이터러블의 조건을 만족하는 것이기도 하므로 이터레이터는 이터러블이다. 따라서 이터러블이 사용되는 곳에 당연히 이터레이터도 쓸 수 있다.)

동작방식의 구체적인 예시는요?

아래처럼 리스트를 만들고 for 문에 이 리스트(이터러블)을 사용해보자. 그럼 아래와 같은 결과가 나타나는데, 어떻게 돌아가는 걸까?
for 가 하는 일을 풀어서 코드로 써보겠다. for 문이 이터러블을 받으면 이터러블의 __iter__() 를 호출한다. __iter__() 메서드는 이터레이터 객체를 리턴한다.
for 문의 각 반복마다 내부적으로 __next__() 메서드를 호출하고 이 메서드는 현재 가르키고 있는 인덱스에 해당하는 원소를 반환하고 (이 반환된 값은 i 에 할당된다), 이터레이터 포인터를 다음 원소로 옮긴다. 모든 원소를 다 접근하고 나면 StopIteration exception이 나오고 for 문이 종료된다.

정리 좀 해주세요!

  • 이터러블은 순회를 당할 수 있는 객체다.
    • __iter__() 또는 __getitem__() 필요
  • 이터레이터는 이터러블의 순회를 주관한다.
    • __iter__()__next__() 필요
    • 이터러블이 될 조건을 만족하므로(__iter__()) 이터레이터는 이터러블이다.
    • 이터러블은 이터레이터가 아니다.
  • 그렇다고 이터러블을 사용할 때, 이터레이터 객체를 직접 다룰 필요는 없다. 이터러블을 사용하는 많은 함수들이 자동으로 이터레이터 객체를 자동으로 생성해준다.
    • for 문은 이터러블을 넣어주면 이터러블 객체의 __iter__() 메서드를 자동으로 호출해서 이터레이터를 생성, 리턴하고 루프를 도는 동안 이름 없는 변수에 잡아둔다.
    • 이터레이션마다 생성한 이터러블 객체의 스페셜 메서드인 __next__()를 호출해서 현재 이터레이터 포인터가 가르키고 있는 곳의 값을 리턴하고, 포인터가 다음 원소를 가르키도록 한다.

🤓 조금 더 들여다볼 사람!

이터레이터는 이터러블이라고 했다. 이터레이터도 __iter__() 가 있기 때문에 이터러블이 된다.
하지만 중요한 예외가 있는데, 리스트와 같은 컨테이너 객체를 iter() 함수에 전달하거나 for 루프에서 사용할 때마다 새 이터레이터를 만든다. 하지만 이터레이터는 이럴때마다 새 이터레이터를 만들지 않고, 지난 이터레이션에 사용된 이미 소진된 이터레이터를 돌려준다. 차이를 코드로 확인해보자.
리스트에 두 번 for 루프를 돌렸는데 똑같이 처음부터 원소가 잘 나온다. 새로운 이터레이터가 for 문마다 자동으로 생성됐기 때문이다.
id를 확인해봐도 계속 새로운 이터레이터가 생긴 것을 알 수 있다.
이터레이터에 for 문을 적용했더니 두번째 for 문에서는 아무것도 출력되지 않는다. 이미 앞서 만들어진 이터레이터에 대한 이터레이터를 리턴했기 때문이다. 계속 같은 이터레이터이기에 id도 같다.

정리하자면 이터레이터에 대한 이터레이터는 한 번 생긴 것을 계속 쓴다. 하지만 이터레이터가 아닌 이터러블에 대한 이터레이터는 계속 새로 만든다.

0개의 댓글