언패킹은 반복을 이용해 list, tuple, dict 같은 컬렉션의 원소를 추출하는 걸 말해요.
이렇게 언패킹한 값은 같은 문 안에서 변수에 할당할 수 있어요.
이런 동작의 아주 흔한 예가 for item in list예요. 여기서 item은 반복하는 동안 list의 각 원소 값을 차례로 가지게 돼요.
다중 할당은 하나의 문 안에서 언패킹한 값들을 여러 변수에 할당할 수 있는 기능이에요.
이를 통해 코드를 더 간결하고 읽기 쉽게 만들 수 있어요. 할당할 변수들을 쉼표로 구분하면 되는데, first, second, third = (1,2,3)이나 for index, item in enumerate(iterable)처럼 써요.
특수 연산자 *와 **는 언패킹할 때 자주 쓰여요.
*는 여러 list/tuple을 하나의 list/tuple로 합칠 때 쓸 수 있어요. 각각을 _언패킹_해서 새로운 공통 list/tuple로 만드는 거예요.
**는 여러 딕셔너리를 하나의 딕셔너리로 합칠 때 쓸 수 있어요. 각각을 _언패킹_해서 새로운 공통 dict로 만드는 거예요.
* 연산자를 컬렉션 없이 사용하면 여러 값을 _패킹_해서 하나의 list로 만들어요.
다중 할당에서는 개별적으로 할당되지 않은 "나머지" 원소들을 하나의 변수로 묶을 때 이 방식을 자주 써요.
Python에서는 위치 인자나 키워드 인자를 임의의 개수만큼 받는 함수를 사용하거나 정의할 때 이 언패킹/패킹 동작을 활용하는 일도 흔해요.
이런 "특별한" 매개변수는 def some_function(*args, **kwargs)처럼 정의하고, "특별한" 인자는 some_function(*some_tuple, **some_dict)처럼 사용하는 걸 자주 보게 될 거예요.
*<variable_name>과 **<variable_name>을 *와 **와 혼동하면 안 돼요. *와 **는 각각 곱셈과 거듭제곱에 쓰이지만, *<variable_name>과 **<variable_name>은 패킹 및 언패킹 연산자로 쓰여요.
다중 할당에서는 할당 연산자(=) 왼쪽의 변수 개수와 오른쪽의 값 개수가 일치해야 해요.
값을 구분하려면 쉼표 ,를 사용해요:
>>> a, b = 1, 2
>>> a
1
다중 할당에서 주어진 값에 비해 변수 개수가 맞지 않으면 ValueError가 발생해요:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
다중 할당은 데이터 타입 하나에 국한되지 않아요:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
다중 할당으로 lists의 원소를 서로 바꿀 수 있어요.
이 방식은 정렬 알고리즘에서 꽤 흔해요.
예를 들어:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
tuples은 변경할 수 없기 때문에, tuple의 원소는 서로 바꿀 수 없어요.
아래 예제들은 lists를 사용하지만, 같은 개념이 tuples에도 적용돼요.
Python에서는 list/tuple/dict의 원소를 언패킹해서 각각 다른 변수에 담을 수 있어요.
lists/tuples 안에서 값은 특정한 순서로 나타나므로, 같은 순서로 변수에 언패킹돼요:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
필요 없는 값이 있다면 _를 써서 표시할 수 있어요:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
list나 tuple 안에 있는 list/tuple에서 값을 언패킹해서 할당하는 것(_중첩 리스트/튜플_이라고도 해요)은 얕은 언패킹과 같은 방식으로 동작하지만, 값의 맥락이나 위치를 명확히 하기 위해 한정자를 자주 붙여야 해요:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
중첩된 list/tuple의 일부분만 깊게 언패킹할 수도 있어요:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
언패킹에서 변수의 위치가 잘못됐거나 값의 개수가 맞지 않으면 ValueError가 발생해요:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
*로 list/tuple 언패킹하기list/tuple을 언패킹할 때 * 연산자를 써서 "나머지" 값을 담을 수 있어요.
이 방법이 list/tuple을 슬라이싱하는 것보다 명확해요(어떤 상황에서는 가독성이 떨어지기도 하죠).
예를 들어, 첫 번째 원소를 추출하고 남은 값들을 첫 번째 원소가 없는 새 list로 패킹할 수 있어요:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
list의 앞과 뒤에 있는 값을 추출하면서 중간에 있는 값들을 모두 묶을 수도 있어요:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
깊은 언패킹에서도 *를 사용할 수 있어요:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
딕셔너리를 언패킹하는 건 list/tuple을 언패킹하는 것과 조금 달라요.
딕셔너리를 반복하면 기본적으로 키를 대상으로 해요.
따라서 dict를 언패킹할 때는 키만 언패킹할 수 있고 값은 언패킹할 수 없어요:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
값을 언패킹하고 싶다면 <dict>.values() 메서드를 사용하면 돼요:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
키와 값이 모두 필요하면 <dict>.items() 메서드를 사용해요.
<dict>.items()는 키-값 쌍을 담은 이터러블 뷰를 만들어요.
이것들은 tuple로 언패킹할 수 있어요:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
패킹은 여러 값을 하나의 list로 묶어 변수에 할당할 수 있는 기능이에요.
값을 _언패킹_해서 변경한 다음, 그 결과를 다시 변수로 _패킹_하고 싶을 때 유용해요.
또한 둘 이상의 lists/tuples/dicts를 병합할 수도 있어요.
*로 list/tuple 패킹하기list/tuple 패킹은 * 연산자로 할 수 있어요.
그러면 모든 값이 list/tuple로 패킹돼요.
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
*와 ** 사용에 대한 자세한 내용은 PEP 3132와 PEP 448을 참고해요.
**로 딕셔너리 패킹하기딕셔너리 패킹은 ** 연산자로 해요.
그러면 한 딕셔너리의 모든 키-값 쌍을 다른 딕셔너리로 패킹하거나, 두 딕셔너리를 하나로 합칠 수 있어요.
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
*와 ** 사용하기임의의 개수만큼 인자를 받는 함수를 만들 때, 함수 정의에 *args 또는 **kwargs를 사용할 수 있어요.
*args는 임의의 개수만큼의 위치(비키워드) 인자를 패킹할 때 쓰고, **kwargs는 임의의 개수만큼의 키워드 인자를 패킹할 때 써요.
*args 사용 예:
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
**kwargs 사용 예:
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args와 **kwargs는 함께 조합해서 사용할 수도 있어요:
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
*args 앞에 매개변수를 써서 특정한 위치 인자를 받도록 할 수도 있어요.
그러면 개별 키워드 인자는 **kwargs 앞에 와야 해요.
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
이 순서를 지키지 않으면 오류가 발생해요.
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
인자를 잘못된 순서로 작성하면 오류가 발생해요:
>>> def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
*를 써서 인자로 이루어진 list/tuple을 함수 호출에 언패킹할 수 있어요.
iterable을 받지 않는 함수에 특히 유용해요:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
zip() 내장 함수와 함께 * 언패킹을 쓰는 것도 흔한 사용 사례예요.
zip() 함수는 여러 이터러블을 받아서, 각 iterable의 값들을 묶은 tuples의 list를 반환해요:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]