무효 클릭 IP 추적 중...
파이썬/파이썬 중급

[파이썬 중급] unpacking에 대해서 잘 알고 계시나요?(*, ** 사용법)

꼬예 2021. 4. 22.

안녕하세요 혹시 여러분은 UNPACKING(언패킹)에 대해서 잘 이해하고 쓰시고 계신가요?

 

아래 문제를 풀어보시겠어요?

 

l1 = [1,2,3]
l2 = ['python']

l3 = [*l1,*l2]
a, *b, (c, *d) = l3
print(a)
print(b)
print(c)
print(d)

a, b, c, d 에 각각 어떤 값이 나올 것 같으신가요.? 

혹시 헷갈리신다면 이번 포스팅을 통해 확실히 개념을 익히시기 바랍니다.. 

 

해당 코드에 정답은 마지막에 공부를 마친 후 다시 보죠!

 

 

 

기본 unpacking

a, b, c = [1, 2, 3]  # 리스트
print(a)
print(b)
print(c)

 

 

 

보시는것처럼 오른쪽에 위치한 리스트의 각 원소들이 위치에  상응하는 a, b, c에 들어가는 형태입니다.

output은 a = 1, b =2, c = 3 이 됩니다. 

 

unpacking은 사실 리스트 뿐 아니라 튜플, string, 딕셔너리 등 모든 iterable 형태의 데이터 타입들은 다 가능합니다.

(*참고 iterable 형태인지 아닌지 궁금하다면 for 문을 돌렸을때 출력이 되는 형태는 다 iterable 형태라고 보면 됩니다.)

 

예시를 통해 정말 그런지 확인 해볼게요.

# string 형태

a, b, c = 'XYZ' # string
print(a,b,c)

output:

# dictionary 형태

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

output:

딕셔너리는 unpacking했을때 key값만 나오는 특징이 있습니다.  

이는 딕셔너리를 for문을 이용해 돌렸을때 key값만 나오는것과  같은 원리라는걸 알 수 있는데요.

 

그렇다면 딕셔너리 뒤에 .values() 나 .items()를 추가하면 value값 또는 (key,value)의 unpacking이 가능하겠구나라는 걸 유추 해볼수 있죠.

 

+ .values()

+ .items()

 

 

참고로 set 도 iterable 이기 때문에 unpacking이 가능은 합니다. 하지만 set은 순서가 없는 데이터 타입니다. 그렇기 때문에 unpacking을 하면 순서대로가 아닌 뒤죽박죽으로 값이 할당되기때문에, 사실상 unpacking의 의미가 없어 사용하지는 않습니다.

 

 

 

*을 통해 unpacking

지금까지는 아주 간단하면서도 직관적인 unpacking이었다면 지금부터는 우리를 조금.. 불편하게 만드는 * , **을 이용해서 어떻게 unpacking을 하는지 알아보도록 하겠습니다. 

 

우선 *가 어디에 위치해 있는지에 따라 전혀 다른 기능을 하니 잘 숙지해주시기 바랍니다. 

 

1. *가 왼쪽 변수에 있을 경우  

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

하지만 위와 같은 코드는 어떨까요? 변수는 3개인데 원소 값이 5개이네요. 하지만 우리는 3개 변수 안에 욱여(?) 넣을 수있는데요. 이때 쓸수 있는 것이 * 입니다. 

 

아래 코드를 보시죠

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


print(a)
print(b) 
print(c)

b 앞에 *를 붙혔습니다. 

*의 기능은 "*가 없는 변수가 값을 할당받고 할당받지 못한 나머지 값들을 모아서 리스트로 만드는" 기능을 한다고 할 수 있습니다. 

무슨말인지는 output을 보면 조금 느낌이 오실 겁니다. 

 

output:

보시는것처럼 *가 없는 a, c에 1, 5가 하나씩 매칭이 되고 *가 있는 변수에 나머지 매칭 안된 값을 모아서 리스트 형태로 리턴하는 것을 볼 수있습니다. 

 

만약 c에 *를 붙힌다면.

a, b가 위치에 상응하는 값을 부여받고 c가 남은 값들을 리스트 형태로 부여받습니다. 

오른쪽에있는 컨테이터타입이 튜플이 오든 SET이 오던 상관없이 항상 리스트 형태 부여받는것도 확인하세요! 

 

+ 참고

왼쪽 변수부분에는 *를 한번만 사용가능합니다. 두번 사용할시 아래와 같이 syntaxError가 나니 주의하세요!

 

2. 컨테이너 그 자체를 unpacking 하는 *

앞선 예시에서는 리스트값을 각 변수에 할당받는데, 값이 변수보다 더 많을 경우에 사용했었죠.

이번에는 리스트 자체를 분해 한다고 보시면됩니다. 

 

아래 코드를 보십시오.

l1 = [1, 2, 3]
print(*l1)

output:

보시는것처럼 리스트값들을 하나하나 분리했습니다. 

그렇다면 이 분리는 해서 어디다 쓰는 것일까요?

 

우선 unpacking 2편에서 다루는 함수에 인자를 넣을때 사용됩니다. 

 

두번째로는 unpacking을 통해 우리가 원하는 컨테이너에 넣어서 합칠 수 있습니다.

l1 = [1, 2, 3]
l2 = [4,5,6]

l3 = [*l1, *l2]

print(l3)

 

output:

*를 통해서 각 리스트에 있는 값들을 분해한후 새로운 리스트에 담았습니다. 

 

* 가 없었다면, 풀어헤치지 않았다는 말이고, 아래와 같이 뭉처진 상태로 리스트를 형성하겠죠?

 

 

이번에는 리스트에 담지 말고 튜플에도 담은 예시를 봅시다!

l2가 리스트가 아닌 string 형태입니다.(string도 iterable이니 각 글자들을 unpacking합니다)

l1 = [1,2,3]
l2 = 'XYZ'
l3 = (*l1, *l2)

print(l3)

output:

이제 어느정도 직관적으로 이해가 되실거라 생각합니다.

*로 풀어헤친다음 튜플로 쌓여있으면 최종 결과는 튜플로 묶이는걸 알 수있죠.

 

 

여기서 문제입니다! 튜플이나 리스트로 싸지않고 그냥 *로 풀어헤치기만 한다면 어떻게될까요?

l1 = [1, 2, 3]
l2 = *l1
print(l2)

 

 

정답은.. 오류입니다.

iterable 자체에 *를 넣는 경우는 값을 분리하는것은 맞습니다. 하지만 그냥 그 자체로는 사용할 수 없습니다.. 

즉, 튜플이든 리스트든 어떤 것으로 둘러쌓야만 하는것이지요.

 

즉 아래와 같이 튜플이든 리스트든 무언가로 싸줘야 오류가 발생하지 않습니다. 

l1 = {1,2,3}
l2 = *l1,
print(l2)

 

 

 

 

output:

+참고

많은 분들이 튜플은 ()로 둘러쌓여 있어야 하는거 아니냐라고 하실텐데요, 물론 ()도 튜플이 맞습니다. 하지만 ()는 더 명확하게 튜플이라는걸 나타내기 위한 보조적인 수단이구요. 튜플을 정의하는 진짜 표현은 " , " 라는거 챙겨 가시면 좋을것같습니다!

 

 

**을 통해 unpacking

이번에는 ** 사용법에 대해서 알아보겠습니다. 

 

**는 딕셔너리를 unpacking하는 목적으로 사용합니다. 

물론 아래와 같은 기능은 제공하지 않구요. 언팩킹 기능만 제공합니다.

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

d1 = {'p':1, 'y':2}
d2 = {'t': 3, 'h' :4}
d3 = {'h':5, 'o': 6, 'n':7}

d = {*d1, *d2, *d3}
print(d)

output:

위 코드는 딕셔너리를 unpacking한후 Set 의 형태로 값을 묶었습니다. 

output을 통해서 몇가지 특징을 찾아볼 수있는데요.

 

첫번째는 *로 unpacking을 하다보니 키 값만 값으로 가져왔습니다.

두번째는 Set 형태로 값을 묶다보니 h가 두개인데 1개로 축소되었고, 순서가 없기때문에 무작위로 섞여 있습니다. 

 

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

 

d1 = {'p':1, 'y':2}
d2 = {'t': 3, 'h' :4}
d3 = {'h':5, 'o': 6, 'n':7}

d = {**d1, **d2, **d3}
print(d)

output:

 

output을 통해 특징을 볼까요?

보시는것처럼 각각의 딕셔너리를 분해하여 새로운 딕셔너리에 차곡차곡 넣은것을 알 수 있습니다. 

이때 보셔야할 것은 중복되어있던 'h'중 하나가 사라졌습니다. 왜냐하면 딕셔너리에선 key가 하나만 올 수 있기때문이죠.더 구체적으로말하면 순서상 늦게 정의된 'h':5 가 이전 값을 override한것입니다.

 

override개념이 이해안되시는분들을 위해 아래에서 조금더 설명해보겠습니다. 

d1 = {'a':1, 'b':2}
d2 = {'a':10, 'c':3, **d1}
print(d2) 

여기선 'a'값이 중복이 되네요? 어떤 값이 최종적으로 남을까요?

output:

 

d2 딕셔너리 내에 d1이 언패킹되면서 뒤이어 'a':1 이 들어오면서 먼저 정의 되어있던 'a': 10을 오버라이드 합니다. 

 

그럼 d2에 구성요소 순서를 아래와 같이 바꾸면 어떻게 될까요?

d1 = {'a':1, 'b':2}
d2 = {**d1, 'a':10, 'c':3}
print(d2) 

output:

이번에는 'a':1이 먼저 정의되었고 그다음 'a':10이 정의 되었으니 뒤에온 'a':10이 살아남았네요:)

 

 

nested unpacking

이번에는 겹겹이 쌓여있는 리스트를 unpacking하는 경우입니다.

 

 

아래 코드와 같이  리스트로 둘러 쌓여있는데 그안에 리스트가 또 있는 형태를 nested 리스트 입니다.

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

 

output:

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)
print(b)
print(c)
print(d)

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

 

output:

 

 

 

이글과 읽으면 좋은글

 

 

 

 

  • 트위터 공유하기
  • 페이스북 공유하기
  • 카카오톡 공유하기
이 컨텐츠가 마음에 드셨다면 커피 한잔(후원) ☕

댓글