티스토리 뷰
파이썬을 다루다 보면 곱셈 연산 이외에 별표(*)가 쓰인 경우가 있다. 이럴 때 쓰이는 별표는 어떤 의미를 가질까?
가령 파이썬의 함수에 대하여 배웠다면 다음과 같이 매개변수에 별표(*args)가 붙는 경우를 볼 수 있다.
1 2 3 4 5 | def sum_all(*args): return sum(args) print(sum_all(1, 2, 3, 4)) # 10 | cs |
왜 이런 꼴이 가능할까? 이번 포스팅에서는 파이썬의 별표, 즉 asterisk 연산자의 다양한 활용에 대하여 알아보자.
1. *operator
파이썬에서의 별표(*) 연산자, 혹은 asterisk 연산자는 집합 형태의 자료형을 풀거나, 묶을 때 사용하는 연산자이다. 우린 이를 unpacking, packing 연산자라 부르기로 하였다.
다음과 같이 하나의 list가 있다고 가정하자.
new_list = [1, 2, 3, 4, 5]
이때 new_list의 모든 원소를 출력하려면 다음과 같이 하나씩 모든 원소에 접근하여 출력하거나, for문을 활용하여 작성할 수 있다.
print(new_list[0], new_list[1], new_list[2], new_list[3], new_list[4])
for i in new_list:
print(i, end = ' ')
하나씩 접근하여 원소를 출럭할 수 있지만, 만약 원소가 100, 1000개, 혹은 그 이상이라면 처음 방법을 어려울 수 있다.
이때 유용하게 사용할 수 있는 것이 unpacking 연산자(*)이다.
만약, for문을 사용하지 않고 별표 연산자를 사용해 출력한다고 하면 다음과 같이 쓸 수 있다.
print(*new_list)
# 1 2 3 4 5
이 경우 '*' 는 곱셈이 아닌 unpacking operator로써 사용된 것이다. 말 그대로 iterable한 자료형을 풀어주는 역할을 한다. 정확히 말하자면 여러 객체를 포함하고 있는 하나의 객체를 풀어주는 역할을 한다.
iterable 자료형들을 unpacking하는 경우 어떤 것들이 나오는지 알아보면 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | a = "Iamastring" print(*a) # I a m a s t r i n g b = [1, 2, 3] print(*b) # 1 2 3 c = ("some", "tuple") print(*c) # some tuple d = range(1, 5) print(*d) # 1 2 3 4 e = set([13, 12, 10, 11, 13]) print(*e) # 10 11 12 13 | cs |
반대로, *는 흩어져있는 자료형을 묶는 것, 즉 패킹(packing)도 가능하다. 다음과 같은 경우 1, 2, 3, 4를 묶어 하나의 list으로 만들어주었고, print 함수로 출력하면 하나의 변수에 담기는 것을 확인할 수 있다.
1 2 3 4 | *x, = 1, 2, 3, 4 # ,가 들어간 이유는 이후에 설명 print(x) # [1, 2, 3, 4] | cs |
*가 묶는 역할도 한다는 것을 알았다면, 왜 2개 이상의 인자를 함수의 매개변수가 f(*args)인지 유추가능하리라 생각한다.
함수에서 가변 인자가 사용되면, packing되어서 함수에 들어가기 때문이라고 생각하면 될 듯하다. packing 될 때는 tuple 형태로 packing 되어 들어간다.
1 2 3 4 5 6 7 8 9 | def func(*args): print(args) func(1, 2, 3) # (1, 2, 3) func(1, 2, 3, 4, 5) # (1, 2, 3, 4, 5) # 출력시 tuple 형태로 출력된다. | cs |
2. Extended iterable unpacking
파이썬이 3.x버젼으로 업그레이드됨에 따라, 파이썬의 * 연산자는 조금 더 복잡한 기능을 지니게 되었다.
먼저, *를 사용하지 않은 언패킹은 다음과 같이 작성할 수 있다.
a, b, c = [1, 2, 3]
# a = 1, b = 2, c = 3
이에 대하여 PEP 3132에서는 extended iterable unpacking이라는 개념이 추가되었다.
PEP 3132의 초록 부분을 살펴보면 다음과 같은 글로 시작한다.
이 PEP(Python Enhancement Proposal은 iterable unpacking 구문에 대한 변경을 제안하고, "일반적인" 이름에 할당되지 않는 모든 항목에 대하여 "모두 넣을" 수 있게 할당할 것이다.
무슨 말이냐 하면, 만약 어떤 list에서 첫 번째 원소와 첫 번째 이외의 원소를 분리한다고 하면 다음과 같이 작성할 수 있다.
first, rest = seq[0], seq[1:]
그러나, 이를 간결하게 하기 위하여 파이썬은 * 연산자를 활용하였다.
first, *rest = seq
* 연산자를 사용하지 않았을 때보다 훨씬 간결해지지 않았는가?
Extended iterable unpacking의 개념은 먼저 *가 붙지 않은 변수에 값을 할당한 다음, 나머지 값들을 할당한다는 개념이다.
만약, 다음과 같은 코드는 이런 방식으로 바뀔 수 있다.
first, middle, last = seq[0], seq[1:-1], seq[-1]
first, *middle, last = seq
할당 순서를 *가 없는 변수부터 마지막으로 남은 데이터들을 *가 있는 변수에 담긴다고 생각하면 된다.
다음은 Extended unpacking에 대한 예시들이다. 길 수 있으나 하나하나씩 찬찬히 읽어보도록 하자. 복잡한 unpacking에 대한 추가 설명은 밑에서 할 예정이다. 90% 이상 맞출 수 있다면 unpacking에 대하여는 문제가 없다고 생각해도 좋다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | a, *b = 1, 2, 3, 4 # a = 1, b = [2, 3, 4] *a, b = 1, 2, 3, 4 # a = [1, 2, 3], b = 4 a, *b, c = 1, 2, 3, 4 # a = 1, b = [2, 3], c = 4 a, *b = 'X' # a = 'X', b = [] *a, b = 'X' # a = [], b = 'X' a, *b, c = "XY" # a = 'X', b = [], c = 'Y' a, *b, c = "WXYZ" # a = 'W', b = ['X', 'Y'], c = 'Z' a, b, *c = 1, 2, 3 # a = 1, b = 2, c = [3] a, b, c, *d = 1, 2, 3 # a = 1, b = 2, c = 3, d = [] a, *b, c, *d = 1, 2, 3, 5, # ERROR - 두 개 이상의 * 연산자가 존재하면 안 된다. (a, b), c = [1, 2], "3" # a = '1', b = '2', c = "3" (a, b), *c = [1, 2], "3" # a = '1', b = '2', c = ["3"] (a, b), c, *d = [1, 2], "3" # a = '1', b = '2', c = "3", d = [] (a, b), *c, d = [1, 2], "3" # a = '1', b = '2', c = [], d = "3" (a, b), (c, *d) = [1, 2], "abc" # a = '1', b = '2', c = 'a', d = ['b', 'c'] *a = 1 # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *a = (1, 2) # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *a, = (1, 2) # a = [1, 2] *a, = 1 # ERROR - int형은 iterable이 아니다. *a, = [1] # a = [1] *a = [1] # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *a, = (1, ) # a = [1] *a, = (1) # ERROR - int형은 iterable이 아니다. *a, b = [1] # a = [], b = 1 *a, b = (1, ) # a = [], b = 1 (a, b), c = 1, 2, 3 # ERROR - unpacking하기에 변수가 너무 많다 (a, b), *c = 1, 2, 3 # ERROR - int형은 iterable이 아니다. (a, b), *c = 'ab', 2, 3 # a = 'a', b = 'b', c = [2, 3] (a, b), c = 1, 2, 3 # ERROR - unpacking하기에 변수가 너무 많다. *(a, b), c = 1, 2, 3 # a = 1, b = 2, c = 3 *(a, b) = 1, 2 # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *(a, b), = 1, 2 # a = 1, b = 2 *(a, b) = "AB" # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *(a, b), = "AB" # a = 'A', b = 'B' *(a, b) = "ABCD" # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다. *(a, b), = "ABCD" # ERROR - unpacking하기에 변수가 너무 많다. *(a, *b), = "ABCD" # a = 'A', b = ['B', 'C', 'D'] *(a, *b), c = "ABCD" # a = 'A', b = ['B', 'C'], c = 'D' *(a, *b), = 1, 2, 3, 4, 5, 6 # a = 1, b = [2, 3, 4, 5, 6] *(a, *b), *c = 1, 2, 3, 4, 5, 6 # ERROR - 두 개 이상의 * 연산자가 존재하면 안 된다. *(a, *b), (*c, ) = 1,2,3,4,5 # ERROR - int형은 iterable이 아니다. *(a, *b), c = 1, 2, 3, 4, 5, 6 # a = 1, b = [2, 3, 4, 5], c = 6 *(a, *b), (*c, ) = 1,2,3,4,"AB" # a = [1], b = [2, 3, 4], c = ['A', 'B'] *(a, *b), c, d = 1, 2, 3, 4, 5, 6, 7 # a = 1, b = [2, 3, 4, 5], c = 6, d = 7 *(a, *b), (c, d) = 1, 2, 3, 4, 5, 6, 7 # ERROR - int형은 iterable이 아니다. *(a, *b), (*c, d) = 1, 2, 3, 4, 5, 6, 7 # ERROR - int형은 iterable이 아니다. *(a, *b), *(c, d) = 1, 2, 3, 4, 5, 6, 7 # ERROR - 두 개 이상의 * 연산자가 존재하면 안 된다. *(a, b), c = "AB", 3 # ERROR - unpack을 위하여 1개 이상의 변수가 필요하다. *(*a, b), c = "AB", 3 # a = [], b = "AB", c = 3 (a, b), c = "AB", 3 # a = "A", b = "B", c = 3 *(a, b), c = "AB", 3, 4 # a = "AB", b = 3, c = 4 *(*a, b), c = "AB", 3, 4 # a = ["AB"], b = 3, c = 4 (a, b), c = "AB", 3, 4 # ERROR - unpacking하기에 변수가 너무 많다. | cs |
이를 해석하는 방법은 다음과 같다.
먼저, '=' 보다 오른쪽에 있는 것들을 풀 수 있다면 모두 tuple형태로 풀린다.
'AB' => ('A', 'B')
['A', 'B'] => ('A', 'B')
만약, 대입해야 하는 변수가 unpack이 필요가 없다면, 그 자체로 진행하면 된다.
또한, 만약 왼쪽과 오른쪽 모두 둘러쌓여 있지 않은 쉼표(,)들이 있다면, 쉼표들끼리 하나의 튜플이라고 생각하면 된다.
'A', 'B' => ('A', 'B')
a, b => (a, b)
다음과 같이 있다고 가정하면, 이를 위의 규칙에 따라 다르게 쓸 수 있다.
(a, b), c = "AB", "C"
((a, b), c) = (('A', 'B'), 'C')
# 따라서 a = 'A', b = 'B', c = 'C'가 들어갈 수 있다.
다른 예제를 살펴보자. 다음과 같은 경우 각각의 변수에 대응할 수 없어서 오류가 발생한다.
(a, b), c = "ABC"
((a, b), c) = ('A', 'B', 'C')
# 오류! 모든 변수에 각각을 대응시킬 수 없다.
오른쪽이 더 복잡해진 경우를 살펴보자. 다음과 같은 경우를 보자.
(a, b), c, = [1, 2], "ABCD"
이 경우 규칙에 따라 다시 써보면 다음과 같이 작성할 수 있다.
((a, b), c) = ((1, 2), ('A', 'B', 'C', 'D'))
c의 경우 unpacking이 불필요하기에, 'ABCD'의 unpacking 되지 않고, 그대로 'ABCD' 문자열로 대입된다.
따라서 다음과 같이 변수가 할당된다.
a = 1, b = 2, c = "ABCD"
이제, 만약 c 부분에 다음과 같이 변형을 취했다고 하자.
(a, b), (c, ) = [1, 2], "ABCD"
규칙에 따라 이를 바꾸면 다음과 같다.
((a, b), (c, )) = ((1, 2), ('A', 'B', 'C', 'D'))
# 오류! 두 번째 tuple의 길이가 달라 대응시킬 수 없다.
이제, 별표 연산자(*)가 있는 경우를 살펴보자. 조금 복잡할 수 있지만, 지금까지 잘 따라왔다면 이해하는 데에 문제가 없을 것이다.
*가 붙은 변수는 list가 된다. 이 list에는 *가 붙지 않은 변수 이외의 데이터들을 담는다고 생각하면 된다.
다음과 같은 경우를 보자. 위의 규칙대로 바꾸면 어떤 변수에 어떤 값이 들어가는지 쉬울 것이다.
a, *b, c = "ABCDE"
(a, *b, c) = ('A', 'B', 'C', 'D', 'E')
# a = 'A', b = ['B', 'C', 'D'], c = 'E'
*가 있는 경우 몇 가지 추가적인 규칙이 붙는다.
먼저, 좌측에는 두 개 이상의 *가 올 수 없다. *가 두 개 이상일 경우 남는 데이터들을 어디로 보내야 할지 애매하기 때문이다.
또한, 왼쪽에 *가 있는 경우 *가 붙은 변수에는 반드시 tuple형 혹은 list형이 와야 한다.
*a = 1
# 오류! *a는 반드시 list 혹은 tuple형이어야 한다.
다음 경우는 어떤가? 규칙에 따라 바꾸면 *a에 어떤 값이 들어가는지 쉽게 알 수 있을 것이다.
*a, = (1, 2)
(*a, ) = (1, 2)
# a = [1, 2]
또한, 만약 오른쪽의 값이 tuple형태로 바꿀 수 없는 경우(즉, iterable이 아닌 경우) 오류가 발생한다.
*a, = 1
(*a, ) = 1
# 오류! int형은 iterable이 아니다
여기서 하나의 예외가 있는데, (1)은 tuple이 아니다. (1)은 정수 1과 같기 때문에 다음과 같이 코드도 오류가 발생한다.
*a, = (1)
# 오류! int형은 iterable이 아님
괄호가 중첩된 경우도 같은 방법으로 해석할 수 있다.
(a, b), *c = "AB", 2, 3
((a, b), *c) = (('A', 'B'), 2, 3)
# a = 'A', b = 'B', c = [2, 3]
괄호끼리 먼저 할당을 한 다음, 순서대로 진행하여주면 된다.
다음과 같이 괄호 앞에 *가 붙는 경우를 보자. 같은 방법으로 해석하여주면 문제없다!
*(a, b), c = 1, 2, 3
(*(a, b), c) = (1, 2, 3)
# a = 1, b = 2, c = 3
이 경우 *가 없는 c부터 차례로 넣어주고, (a, b)에 남는 tuple (1, 2)를 대응시켜주면 된다. 이 경우 양변 모두 길이가 2인 tuple이어서 성공적으로 할당이 된 경우이다.
자, 이제 마지막으로 다음과 같은 경우를 보자. 지금까지의 규칙을 이해하였다면 같은 결론에 이를 수 있어야 한다.
*(a, *b), c = "ABCD"
(*(a, *b), c) = ("A", "B", "C", "D")
# a = 'A', b = ['B', 'C'], c = 'D'
축하한다! 이제 모든 unpacking에 대하여 이해할 수 있는 상태가 되었다!!
만약 모르겠다면, 댓글로 남겨준다면 글을 수정하여 예시를 더 추가하겠다.
(*) 연산자에 대하여 알아보았는데, 생각보다 굉장히 많은 내용에 놀랐을 것이다. 괜찮다. 실제로 *을 쓰는 일은 많이 없고(그나마 함수 매개변수?), unpacking의 경우 더 빠른 방법으로 처리할 수 있기에 모든 것을 알고 가야 한다는 부담감은 놓고 가도 괜찮다.
'Python' 카테고리의 다른 글
[Python] return 문에도 and 와 or를 사용할 수 있다고? 파이썬의 and과 or에 대한 모든 것 (0) | 2022.05.13 |
---|---|
[Python] 리스트 함축(list comprehension)을 시작부터 끝까지 이해하여보자. (0) | 2022.05.12 |
[Python] 가변, 불변(mutable, immutable)에 대한 모든 것 (0) | 2022.05.12 |
[Python] 파이썬은 포인터가 존재하지 않을까? (부제 : id 함수에 관한 모든 것) (0) | 2022.05.12 |
[Python] 파이썬에서 'a == b' 와 'a is b'는 무슨 차이가 있지? (0) | 2022.05.11 |
- Total
- Today
- Yesterday
- 문자열
- Python
- BOJ
- Proactor
- effective async
- 구현
- C
- C++
- 사칙연산
- Network
- 백준
- 제어문
- react
- for
- CSAPP
- GDSC
- equal
- JS
- 시간복잡도
- MIN
- docker
- 프로그래밍
- bomblab
- 수학
- 헤더
- Max
- BRONZE
- 알고리즘
- 함수
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |