최근 파이썬에 갓입문해서 마리아노 아나야의 <파이썬 클린 코드>라는 책을 읽고 있다.
그 중 가변인자부분이 책만 읽어서는 이해가 잘 되지 않아서 적어두려고 한다.
마지막 부분에는 나의 이해를 돕기 위해서 JavaScript도 동일한 결과를 주는 코드를 짜보았다.
iterable 객체를 분해, 병합하는 데 사용한다.
변수 앞에 *를 사용하면 가변 인자를 packing(포장)하여 쓸 수 있다.
def my_func = (*args):
print(args)
my_func(1, 2, 3) #=> (1, 2, 3)
다음, 위치별로 변수에 unpacking(포장을 푸는 것)할 수 있다.
a, b, c = [1, 2, 3]
print(a, b, c) #=> 1 2 3
함수 호출 시 전달할 변수 앞에 *을 붙여서 unpacking할 수도 있다.
def my_func(first, second, third):
print(first, second, third)
my_arr = [1, 2, 3]
my_func(*my_arr) #=> 1 2 3
첫 번째 예제에서 함수 내 파라미터로 쓰인 *args와 위 예제에서 쓰인 *my_arr는 둘 다 변수 앞에 *이 붙었지만 행동하는 양상이 달라서 혼란이 왔다.
정리하자면, 함수 내 파라미터 앞에 *를 붙이면 packing한다는 뜻이고, 함수에 값을 넘길 때는 *를 붙이면 unpacking한다고 볼 수 있다.
아래 예제에서 1, 2, 3은 (1, 2, 3)으로 packing되며 args는 tuple 타입을 가진다.
def my_func = (*args):
print(type(args))
my_func(1, 2, 3) #=> <class 'tuple'>
다음 예제를 살펴보자. 변수 앞에 *을 붙이면 [1, 2, 3]이 unpacking되는 것을 볼 수 있다.
my_arr = [1, 2, 3]
print(my_arr) #=> [1, 2, 3]
print(*my_arr) #=> 1 2 3
그렇다면 변수 할당시에도 *를 붙여야할 것 같은데, 실제로 붙이면 에러가 발생한다.
my_arr = [1, 2, 3]
a, b, c = my_arr # ok
[a, b, c] = my_arr # ok
a, b, c = *my_arr #=> SyntaxError: can't use starred expression here
이 부분에 대해서 인터넷에 검색해 봤는데 잘 찾을 수 없었다. *없이도 a, b, c = my_arr에서 unpacking 작업이 일어난다고 한다.
*을 굳이 붙여서 에러를 발생하지 않게 하려면 변수 다음에 ,를 붙여야 한다. (단순히 unpacking되었다가 packing된다.)
my_arr = [1, 2, 3]
a, b, c = *my_arr,
이것을 응용하면 iterable 객체들을 묶는데 쓸 수 있다.
my_arr1 = [1, 2, 3]
my_arr2 = [4, 5, 6]
my_combined_arr = [*my_arr1, *my_arr2]
print(my_combined_arr) #=> [1, 2, 3, 4, 5, 6]
그 다음, 부분적인 Unpacking도 할 수 있다.
my_arr = [1, 2, 3, 4, 5]
a, b, *rest = my_arr
print(a, b, rest) => 1 2 [3, 4, 5]
끝이 아닌 중간에도 쓸 수 있으며,
my_arr = [1, 2, 3, 4, 5]
a, b, *rest, last = my_arr
print(a, b, rest, last) => 1 2 [3, 4] 5
없을 경우 빈 list를 할당한다.
my_arr = [1, 2, 3]
a, b, *rest, last = my_arr
print(a, b, rest, last) #=> 1 2 [] 3
검색하다가 발견한 stackoverflow 질문에 더 다양한 예제가 있으므로 참고하면 좋을 것 같다.
https://stackoverflow.com/questions/6967632/unpacking-extended-unpacking-and-nested-extended-unpacking
완전히 같지는 않지만, JavaScript의 구조 분해 할당(Destructuring assignment)과 나머지 매개변수(Spread)로 유사하게 동작할 수 있다.
Packing 예제
const myFunc = (...args) => {
console.log(args)
}
myFunc(1, 2, 3) //=> [1, 2, 3]
여기서 typeof args는 "object"이며 Array.isArray(args)는 true를 반환한다.
Unpacking 예제
함수에서 인자를 넘길 때 Unpacking
const myFunc = (a, b, c) => {
console.log(a, b, c)
}
const myArr = [1, 2, 3]
myFunc(...myArr) //=> 1, 2, 3
변수 할당시 Unpacking
const [a, b, c] = myArr
console.log(a, b, c) //=> 1, 2, 3
여기서 python과 다른 동작을 하는 부분이 있다. python에서는 할당할 변수가 [a, b, c] 나 a, b, c 결과가 동일하지만, JS에서는 []를 넣지 않으면 에러가 발생한다.
const [a, b, c] = myArr // ok
const a, b, c = myArr // Uncaught SyntaxError: Missing initializer in const declaration
또 다른 점은 python은 요소의 개수가 동일해야 하지만, JS는 맞지 않아도 상관없다.
# Python 예제
my_arr = [1, 2, 3, 4, 5]
a, b, c = my_arr #=> ValueError: too many values to unpack (expected 3)
// JS 예제
const myArr = [1, 2, 3, 4, 5]
const [a, b, c] = myArr // ok
const [d, e, f, g, h, i] = myArr // ok
console.log(i) //=> undefined
Array 병합 Unpacking
const myArr1 = [1, 2, 3]
const myArr2 = [4, 5, 6]
const myCombinedArr = [...myArr1, ...myArr2]
Extended Unpacking 예제
JS에서도 동일하게 사용할 수 있다.
const myArr = [1, 2, 3]
const [first, ...rest] = myArr
console.log(first, rest) //=> 1, [2, 3]
하지만 중간에서 Unpacking은 할 수 없다.
const myArr = [1, 2, 3]
const [first, ...rest, last] = myArr // Uncaught SyntaxError: Rest element must be last element