이 노트북은 제이크 반더플라스(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])
<list_iterator at 0x7faa76d743c8>
이 반복자가 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))
<range_iterator at 0x7faa76d6dd80>
그래서 파이썬은 이를 마치 리스트처럼 다룹니다:
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
and 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
에는 유용한 반복자가 많이 있습니다. 파이썬 온라인 문서에서 전체 리스트와 예제를 볼 수 있습니다.