Post

파이썬의 이터레이터

파이썬의 이터레이터

도입

이터레이터(Iterator)를 직역하면 반복자라는 뜻이다. 이 이터레이터는 보통 반복 가능하다(Iterable)고 한다.

for문에서의 이터레이터

1
2
3
4
5
6
7
for i in range(5):
  print(i)
# 0
# 1
# 2
# 3
# 4

위는 가장 기본적인 형태의 for문이다. 표준 출력에 0, 1, 2, 3, 4를 순차적으로 출력한다.

구체적으로는 for문 안에서 사용할 변수 i가 생성되어 for문이 진행됨에 따라 순차적으로 0, 1, 2, 3, 4i에 대입된다.

1
2
3
4
5
6
7
8
arr = [10, 3, 4, 5, 7]
for i in arr:
  print(i)
# 10
# 3
# 4
# 5
# 7

위는 range를 리스트로 대체한 형태이다. 이 경우에 for문은 iarr의 각 요소를 순차적으로 대입한다. for문이 진행됨에 따라 10, 3, 4, 5, 7print에 의해 순차적으로 출력된다.

range와 리스트

종종 위 두 사례를 구분된 별개의 건으로 두고 각자 암기하려는 사람들이 있다. 하지만 그것은 적절하지 않다.

1
2
print(list(range(5)))
# [0, 1, 2, 3, 4]
1
2
list(range(5)) == [0, 1, 2, 3, 4]
# True

range(5)[0, 1, 2, 3, 4]와 같게 형변환하면 완전히 동일한 값으로 판별된다. range(5)는 0부터 4까지의 정수 리스트를 생성하는 것과 같다. 그리고 사실 range는 그것을 의도하고 만들어졌다. 일정한 범위의 리스트를 쉽게 생성하기 위한 유틸이다.

다시 말해 for i in range(5):for i in [0, 1, 2, 3, 4]:와 사실상 동일한 결과로 이어진다. for문의 사용법은 두 가지로 구분되는 것이 아니다.

다시 이터레이터

도입하면서 이터레이터를 반복자, 반복 가능하다고 표현했다.

1
2
3
4
sums = 0
for n in [10, 9, 8, 7, 6, 5]:
  sums += n
# sums = 10 + 9 + 8 + 7 + 6 + 5

for <var> in <iter><iter> 부분에는 리스트와, list() 를 통해 리스트가 될 수 있는 range() 가 들어갔다. 이들의 특징은 여러 개의 값을 하나의 값으로 묶어낼 수 있다는 것이다.

여러 개의 값을 하나의 값으로 묶어낼 수 있는 것은 비단 리스트나 range 뿐만이 아니다.

1
2
3
4
5
6
7
8
_tuple = (1, 2, 3, 4, 5)
for t in _tuple:
  print(t)
# 1
# 2
# 3
# 4
# 5

튜플 역시 이터레이터이다.

이터레이터는 for문의 in 키워드 다음에 배치해서 사용할 수 있다. 동시에 for문이 반복을 거듭하면서, for 키워드와 in 사이에 선언한 변수(위 경우에서는 t)에 이터레이터 속 값을 하나씩 꺼내서 대입한다.

1
2
3
4
5
6
_dict = {'username': 'ShapeLayer', 'email': '[email protected]', 'password': 'awesome_password'}
for key in _dict:
  print(key, _dict[key])
# username ShapeLayer
# email [email protected]
# password awesome_password

딕셔너리는 키(Key) 리스트와 값(Value) 리스트, 두 개의 리스트를 만들 수 있다. 딕셔너리에서 키→값 방향의 접근은 용이하지만 값→키 방향의 접근은 어렵다. 그래서 위 상황에서 for문은 딕셔너리의 키 리스트를 반복 과정 중에 사용한다.

1
2
list(_dict)
# ['username', 'email', 'password']

사실 딕셔너리를 리스트로 형변환하면 키 리스트를 획득할 수 있다. for문이 반복 과정에서 사용하는 반복자는 list()를 거쳤다고 생각할 수 있다.

이터레이터는 list()로 리스트 형변환을 하더라도 문제가 발생하지 않는다.

인덱싱과 슬라이싱

이터레이터는 여러 개의 값을 하나의 값으로 묶어낼 수 있다고 했다. 이 특징때문에 그 안의 원소를 사용하려면 인덱싱을 사용해야 한다.

1
2
3
4
5
6
7
8
9
(1, 2, 3, 4, 5)[3] # 인덱스 3번 인덱싱
# 4

_dict = {'username': 'ShapeLayer', 'email': '[email protected]', 'password': 'awesome_password'}
_dict['username'] # 'username' 키 인덱싱
# ShapeLayer

[10, 5, 3, 4, 1][2:4] # [2, 4) 범위 인덱스 슬라이싱
# [3, 4]

이렇게 인덱싱과 슬라이싱이 가능한 또 다른 자료형이 있다. 문자열(str)이다.

1
2
3
4
5
6
7
text = '다람쥐 헌 쳇바퀴에 타고파.'
text[2]
# '쥐'
text[::-1]
# '.파고타 에퀴바쳇 헌 쥐람다'
text[-1]
# '.'

사실 문자열을 문장이라 하지 않고 “문자열”이라고 표현한 것은 컴퓨터가 이걸 “문자”들의 “배열”로 처리하고 있기 때문이다.

특히 C언어에서 문자열을 처리하는 방법에서 이 단어의 유래를 직접 확인할 수 있다.

1
char foo[10] = "foo\0";

파이썬 역시 문자열을 C언어와 비슷하게 문자들의 배열로 처리하므로, 문자열을 인덱싱, 슬라이싱할 수 있다.

문자열을 문자들의 배열로 생각하고 처리하고 있으므로 아예 문자 리스트로 변환할 수도 있다.

1
2
list('다람쥐 헌 쳇바퀴에 타고파')
# ['다', '람', '쥐', ' ', '헌', ' ', '쳇', '바', '퀴', '에', ' ', '타', '고', '파', '.']

사실 문자열은 이터레이터다. 그리고 for문에 넣어도 오류 없이 동작한다.

1
2
3
4
5
6
7
for alphabet in 'apple':
  print(alphabet)
# a
# p
# p
# l
# e

인덱싱, 슬라이싱을 문자열과 리스트 각각 따로 생각할 필요가 없다.

애초에 둘 다 똑같은 이터레이터이므로 똑같이 처리되는건 당연하다.

1
2
3
4
5
'다람쥐 헌 쳇바퀴에 타고파.'[0:3]
# '다람쥐'

['', '', '', ' ', '', ' ', '', '', '', '', ' ', '', '', '', '.'][0:3]
# ['다', '람', '쥐']

마무리

이터레이터는

  • 하나의 값에 여러가지 값을 담을 수 있다.
  • for문에서 in 키워드에 이어서 작성할 수 있다.
  • 인덱싱이 가능하다. (슬라이싱은 모든 이터레이터가 되는 것이 아니다: [:2] [::-1])
  • list() 를 오류 없이 통과한다.
  • 대표 타입: 리스트, 문자열, 튜플, 딕셔너리, 레인지(range)