안녕하세요 혹시 여러분은 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:
이글과 읽으면 좋은글
'파이썬 > 파이썬 중급' 카테고리의 다른 글
[파이썬 중급] @property, @함수명.setter 사용법 (1) | 2021.07.23 |
---|---|
[파이썬 중급] 진법 변환(N진수 -> 10진수 | 10진수 -> N진수) (0) | 2021.06.18 |
[파이썬 중급] map, filter | zip + list comprehension (0) | 2021.05.18 |
[파이썬 중급] *args 사용법( 2편) (0) | 2021.05.04 |
[파이썬 중급] 클래스(CLASS) body 스코프(scope) 개념 정리! (0) | 2021.04.16 |
댓글