티스토리 뷰

반응형

파이썬을 다루다 보면 곱셈 연산 이외에 별표(*)가 쓰인 경우가 있다. 이럴 때 쓰이는 별표는 어떤 의미를 가질까?

 

가령 파이썬의 함수에 대하여 배웠다면 다음과 같이 매개변수에 별표(*args)가 붙는 경우를 볼 수 있다.

 

1
2
3
4
5
def sum_all(*args):
    return sum(args)
 
print(sum_all(1234))
# 10
cs

 

왜 이런 꼴이 가능할까? 이번 포스팅에서는 파이썬의 별표, 즉 asterisk 연산자의 다양한 활용에 대하여 알아보자.

thumbnail


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
= "Iamastring"
print(*a)
# I a m a s t r i n g
 
= [123]
print(*b)
# 1 2 3
 
= ("some""tuple")
print(*c)
# some tuple
 
= range(15)
print(*d)
# 1 2 3 4
 
= set([1312101113])
print(*e)
# 10 11 12 13
cs

반대로, *는 흩어져있는 자료형을 묶는 것, 즉 패킹(packing)도 가능하다. 다음과 같은 경우 1, 2, 3, 4를 묶어 하나의 list으로 만들어주었고, print 함수로 출력하면 하나의 변수에 담기는 것을 확인할 수 있다.

 

1
2
3
4
*x,  = 1234
# ,가 들어간 이유는 이후에 설명
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(123)
# (1, 2, 3)
func(12345)
# (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, *= 1234              # a = 1, b = [2, 3, 4]
*a, b = 1234              # a = [1, 2, 3], b = 4
a, *b, c = 1234           # a = 1, b = [2, 3], c = 4
 
a, *= '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, *= 123              # a = 1, b = 2, c = [3]
a, b, c, *= 123           # a = 1, b = 2, c = 3, d = []
 
a, *b, c, *= 1235,      # ERROR - 두 개 이상의 * 연산자가 존재하면 안 된다.
 
(a, b), c = [12], "3"         # a = '1', b = '2', c = "3"
(a, b), *= [12], "3"        # a = '1', b = '2', c = ["3"]
 
(a, b), c, *= [12], "3"     # a = '1', b = '2', c = "3", d = []
(a, b), *c, d = [12], "3"     # a = '1', b = '2', c = [], d = "3"
 
(a, b), (c, *d) = [12], "abc" # a = '1', b = '2', c = 'a', d = ['b', 'c']
 
*= 1                          # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다.
*= (12)                     # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다.
*a, = (12)                    # a = [1, 2]
*a, = 1                         # ERROR - int형은 iterable이 아니다.
*a, = [1]                       # a = [1]
*= [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 = 123             # ERROR - unpacking하기에 변수가 너무 많다
(a, b), *= 123            # ERROR - int형은 iterable이 아니다.
(a, b), *= 'ab'23         # a = 'a', b = 'b', c = [2, 3]
 
(a, b), c = 123             # ERROR - unpacking하기에 변수가 너무 많다.
*(a, b), c = 123            # a = 1, b = 2, c = 3
 
*(a, b) = 12                  # ERROR - *의 연산자로 대입할 변수는 list나 tuple 형태여야 한다.
*(a, b), = 12                 # 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),  = 123456   # a = 1, b = [2, 3, 4, 5, 6]
 
*(a, *b), *= 123456 # ERROR - 두 개 이상의 * 연산자가 존재하면 안 된다.
*(a, *b), (*c, ) = 1,2,3,4,5    # ERROR - int형은 iterable이 아니다.
*(a, *b), c = 123456  # 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 = 1234567    # a = 1, b = [2, 3, 4, 5], c = 6, d = 7
*(a, *b), (c, d) = 1234567  # ERROR - int형은 iterable이 아니다.
*(a, *b), (*c, d) = 1234567 # ERROR - int형은 iterable이 아니다.
*(a, *b), *(c, d) = 1234567 # 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"34                 # a = "AB", b = 3, c = 4
*(*a, b), c = "AB"34                # a = ["AB"], b = 3, c = 4
(a, b), c = "AB"34                  # 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의 경우 더 빠른 방법으로 처리할 수 있기에 모든 것을 알고 가야 한다는 부담감은 놓고 가도 괜찮다.

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
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
글 보관함