Profile picture

[Python] Packing, Unpacking (*, ** 사용법)

JaehyoJJAng2023년 06월 04일

기본 unpacking

파이썬 unpacking(언패킹)에 대해서 잘 이해하고 있나요?

t1 = [1,2,3]
t2 = ['test']

t3 = [*t1,*t2]
print(t3)

a, *b, (c,*d) = t3
print(a)
print(b)
print(c)
print(d)

a,b,c,d 변수에 어떤 값이 들어갈 것인지 예측이 됩니까?
헷갈린다면 개념을 확실하게 공부해가도록 하자.


a,b,c = [1,2,3]
print(a,b,c)

보이는 것처럼 오른쪽에 위치한 리스트의 각 원소들이 위치에 상응하는 a,b,c 변수에 들어가는 형태이다.
아웃풋은 a = 1 , b = 2 , c = 3이 된다


unpacking은 리스트 뿐 아니라 튜플 , string , 딕셔너리 등 모든 iterable 형태의 데이터 타입들은 모두 가능하다.(* 데이터 타입이 iterable 형태인지 아닌지 궁금하다면 for 문을 돌렸을 때 출력이 되는 형태는 모두 iterable 형태라고 보면 된다 )

예시를 통해 정말 그런지 테스트 해보자


string 형태

a,b,c = 'WOW'
print(a,b,c)
# Output
W O W

Dictionary 형태

a,b,c = {'key1': 1, 'key2': 2, 'key3': 3}
print(a b c)
# Output
key1 key2 key3

딕셔너리는 unpacking을 했을 때 key 값만 나오는 특징이 있다.
이는 딕셔너리를 for문을 이용해 돌렸을 떄 key 값만 나오는 것과 같은 원리라는 것을 알 수 있는데, 그렇다면 딕셔너리 뒤에 .values(), .items() 함수를 추가하면 value값 또는 (key,value)의 unpacking이 가능하겠구나라는 걸 유추해볼 수 있겠다.


values()

a,b,c = {'key1': 1, 'key2': 2, 'key3': 3}.values()
print(a,b,c)
# Output
1 2 3

items()

a,b,c = {'key1': 1, 'key2': 2, 'key3': 3}.items()
print(a,b,c)
# Output
('key1', 1) ('key2', 2) ('key3', 3)

참고로 set 자료형또한 iterable 타입이기 때문에 unpacking이 가능하다. 하지만 set은 순서가 없는 데이터 타입이므로, unpacking을 하게되면 데이터가 뒤죽박죽이 되기 때문에 사용되지는 않는다


*을 통한 unpacking

우선 *가 어디에 위치하냐에 따라 전혀 다른 기능을 하니 잘 숙지해야 한다.


*가 왼쪽 변수에 있을 경우

a,b,c = [1,2,3]

기존에는 위와 같이 코드를 적으면 a,b,c 순서에 맞게 1,2,3이 잘 들어갔었다.


a,b,c = [1,2,3,4,5,6]

하지만 위와 같은 코드는 어떤가? 변수는 3개인데 리스트 안의 원소 6개나 존재하고있다. 이럴 때 쓸 수 있는 것이 *이다.


아래 코드를 봐보자

a,*b,c = [1,2,3,4,5,6]

b 앞에 *를 붙였다.
*의 기능은 **가 없는 변수가 값을 할당받고 할당받지 못한 나머지 값들을 모아서 리스트로 만드는 기능을 한다고 보면 될 것 같다

# Output
1
[2,3,4,5]
6

보이는 것처럼 *가 없는 a,c 변수에 각각 1, 6이 할당 되었고 *가 있는 변수에 나머지 매칭안된 값을 모아서 리스트 형태로 반환하고 있다.

참고!

왼쪽 변수부분에는 *를 한 번만 사용가능하다.


컨테이너 자체를 unpacking 하는 경우

위 예시에서는 리스트 값을 각 변수에 할당받는데, 리스트 안의 원소가 변수보다 더 많을 경우에 사용했었다면 이번에는 리스트 자체를 분해 한다고 보면 될 것 같다.

아래 코드를 봐보자

t1 = [1,2,3]
print(*t1)
# Output
1 2 3

보이는 것처럼 리스트 값들이 하나씩 분해되었다.
그렇다면 리스트를 분리해서 어디다 쓰는 것일까?


1. 함수에 인자를 넣을 때 사용된다.

def func_arg(t1:int ,t2: int,t3: int) -> int:
    return (t1 + t2 + t3)
nums = [5,10,1]
func_arg(*nums)

위 코드를 봐보자. func_arg 함수에서 t1,t2,t3 라는 3개의 매개변수를 요구하고있다. 함수에 매개변수가 많이 존재할 때 우리는 위와 같이 리스트나 튜플 데이터를 언패킹해서 함수에 전달할 수 있다


2. 원하는 컨테이너에 합치기

t1 = [1,2,3]
t2 = [4,5,6]

t3 = [*t1,*t2]
print(t3)
# Output
[1,2,3,4,5,6]

*를 통해서 각 리스트에 있는 값들을 분해하고 새로운 리스트에 담았다.


튜플로도 unpacking 하기

위에 예시에서 리스트로만 예시를 들었었는데 이번에는 튜플에도 담은 예시를 봐보자.
t2가 리스트가 아닌 string 형태이다.(위에서 말했듯 string 또한 iterable이니 각 글자들을 unpacking 할 수 있다)

t1 = [1,2,3]
t2 = 'WOW'
t3 = (*t1,*t2)
print(t3)
# Output
(1,2,3,'W','O','W')

이제 어느정도 이해가 되었는가?
*로 분해한다음 튜플로 쌓여있으면 최종 결과는 튜플로 묶이는 걸 알 수가 있다.


*그렇다면, 여기서 튜플로 감싸지 않고 로 바로 분해해버리면 어떻게 될까?

t1 = [1,2,3]
t2 = *t1
print(t2)

정답은 .. 땡! 오류가 난다

    t2 = *t1
         ^^^
SyntaxError: can't use starred expression here

iterable 자체에 *를 넣는 경우는 값을 분리하는 것은 맞지만 그냥 그 자체로는 사용할 수 없다. 즉 튜플이든 리스트든 어떤 것으로 둘러쌓여야만 한다는 것이다


아래와 같이 튜플이나 리스트로 감싸줘야 위와 같은 에러가 발생하지 않는다

t1 = [1,2,3]
t2 = (*t1)
print(t2)

**을 통한 unpacking

**는 딕셔너리를 unpacking 하는 목적으로 사용한다. 물론 아래 코드와 같은 기능은 제공하지 않고, 오로지 unpacking 기능만 제공함

a , **b , c = {'a':1 , 'b': 2 , 'c': 3}

코드를 통해 자세히 알아보자

t1 = {'a':1 , 'b': 2 }
t2 = {'c': 3, 'd': 4}
t3 = {'d': 5, 'f': 6, 'g': 7}

r = {*t1, *t2 , *t3}
print(r)
# Output
{'a', 'd', 'f', 'g', 'c', 'b'}

위 코드는 딕셔너리를 unpacking 한 후 Set 자료형 타입으로 값을 묶었다.


output을 살펴보면 몇가지 특징이 있는데

  • 첫 번째로 *로 unpacking을 하다보니 키 값만 값으로 가져오게 되었다.
  • 두 번째로 Set 형태로 값을 묶다보니 d가 두 개 인데도 불구하고 1개로 축소되었고, 순서가 없기 때문에 데이터 순서가 뒤죽박죽이다.

이럴 때 key와 value를 한꺼번에 보기 위해서 사용하는 것이 **이다.

t1 = {'a':1 , 'b': 2 }
t2 = {'c': 3, 'd': 4}
t3 = {'d': 5, 'f': 6, 'g': 7}

r = {**t1, **t2 , **t3}
print(r)
# Output
{'a': 1, 'b': 2, 'c': 3, 'd': 5, 'f': 6, 'g': 7}

output을 통해 특징을 살펴보자.
보이는 것처럼 각각의 딕셔너리를 분해하여 새로운 딕셔너리에 차곡차곡 넣은 것을 알 수 있다.
이 때 자세하게 봐야할 부분은 중복되어있던 'd' 중 하나가 사라졌다. 왜냐하면 딕셔너리에선 key가 하나만 올 수 있기 때문이다. 더 구체적으로 설명하자면 순서상 늦게 정의된 'd':5 가 이전 값을 override 한 것이라고 보면 될 것 같다.


nested unpacking

nested unpacking은 겹겹이 쌓여있는 리스트를 unpacking 하는 경우이다.


아래 코드와 같이 리스트로 둘러 쌓여있는데 그 안에 리스트가 또 있는 형태를 nested 리스트라고 지칭한다

t = [1,2,[3,4]]
a,b,c,d = t
print(a,b,c,d)
# Output
    a,b,c,d = t
ValueError: not enough values to unpack (expected 4, got 3)

output을 확인해보니 not enough values to unpack 이라는 valueerror 가 발생하였다.
이 에러는 a는 1 b는 2 c는 [3,4] 리스트를 통째로 받고 d는 아무것도 받지 않아서 생긴 에러이다. 현재 내가 원하는건 [3,4] 리스트의 원소가 각각 c,d에 들어갔으면 하는데 이걸 어떻게 해야할까?


이 문제를 해결하기 위해선 아래와 같이 코드를 작성하면 된다

a,b,(c,d) = [1,2,[3,4]]
print(a,b,c,d)
# Output
1 2 3 4

즉 a는 1을 , b는 2을 , (c,d)는 두 개의 변수이지만 ()로 덮음으로써 가장 바깥쪽 리스트에선 하나의 변수 역할을 하게된다. (c,d)가 [3,4] 리스트를 받고 그 후에 c,d가 3,4를 각각 받게 되는 것이다!


Loading script...