반복자

이 노트북은 제이크 반더플라스(Jake VanderPlas)의 A Whirlwind Tour of Python(OReilly Media, 2016)를 기반으로 만들어졌습니다. 이 내용은 CC0 라이센스를 따릅니다. 전체 노트북의 목록은 https://github.com/rickiepark/WhirlwindTourOfPython 에서 볼 수 있습니다.

< 에러와 예외처리 | 목차 | 리스트 내포 >

 

데이터 분석의 중요한 한 방법은 비슷한 계산을 자동으로 계속 반복하는 것입니다. 예를 들어 성과 이름으로 분리하고 싶은 사람 이름이 들어 있는 테이블이 있거나, 표준 포맷으로 바꾸어야 하는 날짜의 테이블이 있는 경우입니다. 이에 대한 파이썬의 대답은 반복자 문법입니다. 이전에 이미 range 반복자를 보았습니다:

for i in range(10):
    print(i, end=' ')
0 1 2 3 4 5 6 7 8 9

여기서는 조금 더 자세히 알아 보겠습니다. 파이썬 3에서 range는 리스트가 아니고 반복자라고 부르는 어떤 것입니다. 이를 배우는 것이 파이썬의 유용한 많은 기능을 이해하는데 핵심적인 역할을 할 것입니다.

리스트를 반복하기

반복자는 아마도 리스트를 순회하는 예제를 통해서 가장 쉽게 이해할 수 있습니다.
다음 예를 보죠:

for value in [2, 4, 6, 8, 10]:
    # 어떤 작업을 수행합니다
    print(value + 1, end=' ')
3 5 7 9 11

for x in y” 문법은 리스트에 있는 각 원소에 대해 어떤 연산을 반복하도록 만들어 줍니다.
이 코드의 문법이 영어의 문장(“for [each] value in [the] list“)과 비슷한 것은 파이썬을 배우고 사용하기 쉬운 언어로 만들기 위한 선택 중 하나일 뿐입니다.

하지만 보이는 모습이 실제로 일어나는 것과는 다릅니다. “for val in L“와 같이 쓰면, 파이썬 인터프리터는 반복자 인터페이스를 가졌는지 확인합니다. 여러분이 직접 내장 iter 함수를 사용하여 확인할 수도 있습니다:

iter([2, 4, 6, 8, 10])

이 반복자가 for 루프에서 필요한 기능을 제공합니다. iter 객체는 내장 함수 next로 유효한 다음 객체를 참조할 수 있습니다:

I = iter([2, 4, 6, 8, 10])
print(next(I))
2
print(next(I))
4
print(next(I))
6

이렇게 반복자를 둔 이유가 무엇일까요? 파이썬에서 실제로 리스트가 아닌 것을 리스트처럼 다룰 수 있기 때문에 아주 유용한 기능입니다.

range(): 리스트가 항상 리스트는 아니다

이런 간접적인 반복의 가장 흔한 예제는 파이썬 3의 range() 함수입니다(파이썬 2에서는 xrange()). 이 함수는 리스트가 아니라 특별한 range() 객체를 반환합니다:

range(10)
range(0, 10)

리스트와 마찬가지로 range는 반복자를 가집니다:

iter(range(10))

그래서 파이썬은 이를 마치 리스트처럼 다룹니다:

for i in range(10):
    print(i, end=' ')
0 1 2 3 4 5 6 7 8 9

간접적인 반복자의 장점은 전체 리스트가 완전히 생성되지 않는다는 것입니다! 만약 실제로 만들어졌다면 시스템 메모리를 초과하는 범위를 만들어 봄으로써 알 수 있습니다(파이썬 2에서는 range가 리스트를 만들기 때문에 다음을 실행하면 좋은 결과를 얻지 못할 것입니다!):

N = 10 ** 12
for i in range(N):
    if i >= 10: break
        print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

range가 실제로 1조개의 리스트를 만들었다면 10테라 바이트의 메모리가 필요합니다. 여기서는 단지 10개의 값만 필요하기 때문에 이는 낭비입니다!

사실 반복자가 끝을 가질 필요가 전혀 없습니다! 파이썬의 itertools 라이브러리는 무한한 범위를 가진 것처럼 동작하는 count 함수를 가지고 있습니다:

from itertools import count

for i in count():
    if i >= 10:
        break
    print(i, end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

break 문을 사용하지 않으면 프로세스 직접 중지시키거나 종료하기 전까지(예를 들어 ctrl-C로) 계속 카운트가 증가할 것입니다.

유용한 반복자

반복자 문법은 나중에 보게될 데이터 과학 라이브러리 뿐만 아니라 파이썬의 내장 타입에 광범위하게 사용됩니다. 파이썬 언어에서 유용한 대표적인 반복자 일부를 살펴 보겠습니다:

enumerate

종종 배열에 있는 값 뿐만 아니라 인덱스를 반복해야할 때도 있습니다. 다음과 같이 할지도 모르겠습니다:

L = [2, 4, 6, 8, 10]
for i in range(len(L)):
    print(i, L[i])
0 2
1 4
2 6
3 8
4 10

이렇게 해도 되지만 파이썬에서는 enumerate 반복자를 사용한 더 깔끔한 문법을 제공합니다:

for i, val in enumerate(L):
    print(i, val)
0 2
1 4
2 6
3 8
4 10

이것이 리스트에 있는 인덱스와 값을 나열하는 더 파이썬스러운 방법입니다.

zip

어떨 때는 여러개의 리스트를 동시에 반복해야 할 필요가 있습니다. 앞서 본 파이썬스럽지 않은 예에서처럼 인덱스를 반복할 수 있지만 zip 반복자를 사용하여 함께 반복할 것을 묶어주는 것이 더 좋습니다:

L = [2, 4, 6, 8, 10]
R = [3, 6, 9, 12, 15]
for lval, rval in zip(L, R):
    print(lval, rval)
2 3
4 6
6 9
8 12
10 15

반복할 수 있는 것은 몇 개든 함께 쓸 수 있습니다. 길이가 다를 경우 가장 짧은 것이 zip의 길이를 결정합니다.

map과 filter

map 반복자는 함수를 받고 반복자의 값을 적용합니다:

# 10까지 수를 제곱합니다
square = lambda x: x ** 2
for val in map(square, range(10)):
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81

filter 반복자는 비슷하지만 필터 함수가 참으로 평가한 값만 통과시킵니다:

# 10까지 x % 2이 0이 되는 수를 출력합니다
is_even = lambda x: x % 2 == 0
for val in filter(is_even, range(10)):
    print(val, end=' ')
0 2 4 6 8

map, filter 함수와 reduce 함수(파이썬의 functools 모듈에 있습니다)는 함수형 프로그래밍 스타일의 기본 요소입니다. 파이썬 세계의 주류 프로그래밍 스타일은 아니지만 열렬한 지지자도 있습니다(예를 들면 pytoolz 라이브러리).

함수 매개변수로서의 반복자

유연한 매개변수인 *args**kwargs를 보았습니다. *args**kwargs는 함수에게 시퀀스와 딕셔너리를 전달하는데 사용됩니다. *args 문법은 시퀀스뿐만이 아니라 반복자와 함께 쓰일 수 있습니다:

print(*range(10))
0 1 2 3 4 5 6 7 8 9

예를 들어 이전의 map 예를 다음과 같이 압축해서 쓸 수 있습니다:

print(*map(lambda x: x ** 2, range(10)))
0 1 4 9 16 25 36 49 64 81

이런 기법을 사용하면 파이썬 포럼에 올라오는 오래된 질문에 대답을 할 수 있습니다. “왜 zip()에 반대되는 unzip() 함수는 없나요?” 잠시 눈을 감고 생각해 보면 zip()의 반대는 zip()이라는 것을 알게 될 것입니다. zip()은 몇 개의 반복자나 시퀀스로 모두 묶을 수 있다는 것이 핵심입니다. 한 번 살펴 보죠:

L1 = (1, 2, 3, 4)
L2 = ('a', 'b', 'c', 'd')
z = zip(L1, L2)
print(*z)
(1, 'a') (2, 'b') (3, 'c') (4, 'd')
z = zip(L1, L2)
new_L1, new_L2 = zip(*z)
print(new_L1, new_L2)
(1, 2, 3, 4) ('a', 'b', 'c', 'd')

잠시 생각해 보세요. 왜 이렇게 동작하는지 이해했다면 파이썬의 반복자를 많이 이해한 것입니다!

특별한 반복자: itertools

무한 range 반복자인 itertools.count를 잠시 보았습니다. itertools 모듈은 유용한 반복자를 모두 담고 있습니다. 모듈에 담긴 내용을 살펴 볼 가치가 충분히 있습니다. 한가지 예로 시퀀스의 모든 순열을 반복해 주는 itertools.permutations 함수를 살펴 보겠습니다:

from itertools import permutations
p = permutations(range(3))
print(*p)
(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)

비슷하게 itertools.combinations 함수는 리스트에서 N 개를 뽑아 중복되지 않은 조합을 반복합니다:

from itertools import combinations
c = combinations(range(4), 2)
print(*c)
(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)

이는 두 개 이상의 반복 가능한 쌍의 집합을 반복해 주는 product 반복자와 관련이 있습니다:

from itertools import product
p = product('ab', range(3))
print(*p)
('a', 0) ('a', 1) ('a', 2) ('b', 0) ('b', 1) ('b', 2)

itertools에는 유용한 반복자가 많이 있습니다. 파이썬 온라인 문서에서 전체 리스트와 예제를 볼 수 있습니다.

 

< 에러와 예외처리 | 목차 | 리스트 내포 >

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.