개요
본 포스트에서는 Python의 zip() 함수에 대해 알아본다. zip() 함수는 iterable(순회 가능한 객체)들을 원소 단위로 묶어주는 역할을 수행한다. zip() 함수를 통해 데이터를 전처리할 때, Pythonic하고 유연하게 수행할 수 있는 장점이 있다.
사용 예시
a = ['a', 'b', 'c']
b = [1, 2, 3]
zip(a, b) # <zip object at 0x000001C29CBE3AC8>
- zip() 함수의 반환값은 입력으로 주어진 iterable의 타입이 아닌 zip 타입의 객체가 반환되는 것을 확인할 수 있다.
for pair in list(zip(a, b)):
print(pair)
>>
('a', 1)
('b', 2)
('c', 3)
- zip 인스턴스를 list로 캐스팅하고 for loop를 돌린 결과 다음과 같이 두 iterator의 데이터가 각각 쌍을 맺은 것을 확인할 수 있다.
- a list의 첫 번째 원소 'a'와 b list의 첫 번째 원소 1이 튜플로 묶인 ('a', 1) 원소를 첫 번째 루프에서 반환
- a list의 두 번째 원소 'b'와 b list의 두 번째 원소 2이 튜플로 묶인 ('b', 2) 원소를 두 번째 루프에서 반환
- a list의 세 번째 원소 'c'와 b list의 세 번째 원소 3이 튜플로 묶인 ('c', 3) 원소를 세 번째 루프에서 반환
for n, u, l in zip('123', 'ABC', 'abc'):
print(n, u, l)
>>
1 A a
2 B b
3 C c
- 또한, 묶은 원소들을 각 개별 원소로 분리할 수도 있다.
- '1Aa' -> n, u, l -> '1', 'A', 'a'
- '2Bb' -> n, u, l -> '2', 'B', 'b'
- '3Cc' -> n, u, l -> '3', 'C', 'c'
pairs = list(zip(a, b)) # [('a', 1), ('b', 2), ('c', 3)]
# a는 ('a', 'b', 'c')
# b는 (1, 2, 3)
a, b = zip(*pairs)
- zip() 메소드의 인자로 *을 붙이면 unzip이 가능하다. 원리를 알아보자.
# 가변 인자
# *을 붙이면 각 요소가 함수의 인자로 전달된다. args1=('a', 1) args2=('b', 2)
def f(args1=None, args2=None):
print(args1, args2)
l = [('a', 1), ('b', 2)]
f(*l)
- 앞서 배웠던 가변 인자의 개념을 복습하자면, 길이가 변할 수 있는 iterable(e.g. list)을 함수의 인자로 전달하게 되면 각 원소가 함수의 각 파라미터에 대응되게끔 주입된다.
pairs # [('a', 1), ('b', 2), ('c', 3)]
zip(*pairs) # 이 부분의 인자는 arg1=('a', 1) arg2=('b', 2) arg3=('c', 3) 총 세개가 전달됬을 것이다.
list(zip(*pairs)) # [('a', 'b', 'c'), (1, 2, 3)]
- pair(tuple로 이루어진 list)를 zip() 함수에 가변인자로 전달하게 되면, 각 원소가 zip() 함수에 각 인자로 전달된다.
- 전달된 인자도 iterable(tuple)이기 때문에 각 tuple들의 첫 번째 원소들('a', 'b', 'c')끼리, 두 번째 원소들(1, 2, 3)끼리 묶이게 된다.
# 리팩터 이전
def mapping_dict_values_with_tuple(d: dict, t: tuple):
for i, key in enumerate(d):
d[key] = t[i]
return d
# 리팩터 이후
def mapping_dict_values_with_tuple(d: dict, t: tuple) -> dict:
"""
Maps the values of a dictionary to a tuple.
Args:
d (dict): The dictionary whose values are to be mapped.
t (tuple): The tuple to map the values to.
Returns:
dict: A new dictionary with the same keys as `d` and values from `t`.
"""
return {k: v for k, v in zip(d.keys(), t)}
예를 들어, 딕셔너리에 여러값을 담고 있는 iterator(여기서는 튜플)를 value에 자동으로 대입하는 로직을 작성해본다고 가정하자. 물론 for 문을 통해 전처리 로직을 작성할 수 있지만, 위와 같은 로직이 지속적으로 반복될 경우, 보일러 플레이트로 작용할 수 있다. 복잡한 로직을 처리해야할 경우, for 문의 사용은 불가피하겠지만, 그렇지 않으면 comprehension을 통해 로직을 작성하는 게 아래와 같은 이점들이 있다.
- 로직을 단순화할 수 있다.
- 시간 복잡도 관점에서 둘다 O(N)으로 동일하지만, comprehension은 C로 구현되어 있고, for loop은 __next__(), __iter__()와 같은 Python의 매직 메소드로 구현되어 있기 때문에 (인터프리트 하는 과정에서) 상수 시간 소요 관점에서 comprehension이 2~3배 정도 빠르다.
dictionary comprehension과 동시에 zip() 함수를 통해 각각의 딕셔너리의 키들과 튜플의 값들을 매핑한 새로운 딕셔너리를 반환하는 함수로 리팩터링한 모습이다. 이처럼 단순히 zip() 함수를 통해서만이 아닌 다른 문법들과도 조합을 통해 새로운 데이터 전처리 로직을 유연하게 구현하는 경우도 있다.
'[Language] - Python' 카테고리의 다른 글
| 함수형 프로그래밍 (0) | 2021.03.25 |
|---|---|
| [Tip] if문 분기와 삼항연산자가 사용하기 싫을 때 (0) | 2021.03.24 |
| property 데코레이터 (8) | 2021.02.02 |
| 덕 타이핑 (Duck Typing) (2) | 2020.12.29 |
| 추상 베이스 클래스 (Abstract Base Class) (0) | 2020.12.28 |