본문 바로가기

진리는어디에/Python

[Python] 함수 #3 파라미터 언패킹

이번에 살펴볼 파라미터 패킹과 언패킹은 포스트가 길어져 두 장에 나뉘어 작성 되었다. 이번 포스트에서는 언패킹에 대해 알아보고, 바로 이어지는 다음 포스트에서 패킹에 대해 같이 알아보도록 하겠다.

목차

  1. 함수 소개
  2. 디폴트 파라미터 주의 사항
  3. >> 파라미터 언패킹
  4. 파라미터 패킹
  5. 함수 객체
  6. 일급 객체(first class object)
  7. 람다(lambda)

파라미터 언팩(unpack)

파라메터 언패킹(unpacking)이란 시퀀스 객체(list, tuple, set, dictionary)가 함수의 인자로 넘겨지는 경우, 시퀀스 객체의 각 요소들을 개별 변수로 풀어서, 각각의 변수로 함수의 인자로 전달하는 기능을 말한다. 글로 설명하면 설명이 어려우니 빠르게 예제 중심으로 설명하도록 한다.

언패킹이란?
"시퀀스 객체의 각 요소들을 개별 변수로 풀어서, 각각의 변수로 함수의 인자로 전달하는 기능"

먼저 아래와 같이 인자를 세개 받는 foo() 함수와 각 요소가 세개씩 들어 있는 시퀀스 데이터들이 있다고 하자.

def foo(a, b, c) :
    print(a, b, c)
    
l = [1, 2, 3]             # list
t = (1, 2, 3)             # tuple
s = {1, 2, 3}             # set
d = {'a':1, 'b':2, 'c':3} # dictionary

foo(1, 2, 3)
foo(l)       # TypeError: foo() missing 2 required positional arguments: 'b' and 'c'

함수 foo()를 9라인 처럼 세개의 인자로 호출하는 것은 아무런 문제가 없다. 하지만 10라인에서 처럼 리스트 객체 달랑하나를 인자로 호출하려고 시도하면 b와 c 포지션에 매칭되는 인자가 없다는 에러를 발생 시킨다.

이때 아래와 같이 인자로 넘겨지는 시퀀스 데이터 앞에 *를 붙여 넘겨주면 아무런 에러가 발생하지 않고 정상적으로 실행 되는 것을 볼 수 있다(딕셔너리 데이터 d가 빠져 있는데, 이는 다음 항목에서 바로 설명 한다).

foo(*l)  # 1 2 3
foo(*t)  # 1 2 3
foo(*s)  # 1 2 3

왜 일까? 이것이 바로 파라메터 언패킹이다. 위에서 언패킹이란 "시퀀스 객체의 각 요소들을 개별 변수로 풀어서, 각각의 변수로 함수의 인자로 전달하는 기능"이라고 했다. 함수 인자로 넘겨지는 시퀀스 객체 앞에 *를 붙이면, 객체의 각 요소들을 풀어서 함수의 인자 순서에 맞게 넘겨 준다.(positional arguement 참고)

예를들어 위 예제에서 리스트 객체 l의 경우 첫번째 요소인 1은, 함수의 첫번째 인자인 a, 두번째 요소인 2는 함수의 두번째 인자 b, 세번째 3은 세번째 인자인 c와 매칭된다. tuple, set도 동일한 방법으로 함수의 인자들과 매칭 되어 넘겨진다.

dictionary의 경우는 key와 value로 구성되어 있어 다른 시퀀스 데이터들과는 처리가 다르다. 딕셔너리 시퀀스 앞에 *를 붙인다는 것은 딕셔너리의 키들을 각 함수의 인자에 맞춰서 넘겨 준다.

foo(*d)  # a b c

위와 같이 딕션너리 데이터 앞에 *를 붙여주면 딕셔너리의 키 'a', 'b', 'c'를 함수 foo의 인자의 순서에 맞게 넘긴다. 하지만 아래와 같이 딕셔너리 데이터 앞에 *를 두개 연속으로 붙여주면 딕셔너리의 값들을 함수 인자의 이름과 매칭하여 keyword argument 형태로 전달한다는 의미다.

foo(**d) # a=1, b=2, c=3 과 같은 의미

키가 a인 1은 foo함수의 a인자에, b인 2는 b인자에, c는 c인자에 매칭하여 foo함수에게 전달 된다.

딕셔너리 시퀀스 언패킹 시
* 는 키만 언패킹, **는 값을 언패킹

파라미터 unpack 주의 사항

당현하지만 의외로 실수를 많이 하는 부분이 언패킹되는 시퀀스 객체의 요소 개수와 함수의 인자 개수가 다르거나, 딕셔너리 언패킹시 딕셔너리의 키워드와 함수 인자리스트를 다르게 하는 것이다.

파라메터 언패킹을 사용하기 위해서는 시퀀스 객체의 요소 수와 파라메터 수가 일치해야 한다. 또한 딕셔너리의 경우 **를 이용해서 keyword argument 형태로 언패킹하는 경우, 파라메터 이름과 딕셔너리의 키가 동일해야 한다.

def foo(a, b, c = 5) :
    print(a, b, c)
    
l4 = [1, 2, 3, 4]
foo(*l4)       # TypeError: foo() takes from 2 to 3 positional arguments but 4 were given

l2 = [1, 2]     
foo(*l2)       # ok, 요소가 두개지만 c가 디폴트 파라메터이므로 문제 없다.

d = {'a':1, 'b':2, 'd':3} 
foo(*d)        # ok, 인자로 'a', 'b', 'd'가 넘어가므로 문제 없다.
foo(**d)       # TypeError: foo() got an unexpected keyword argument 'd'
               # 없는 함수 인자 이름 d를 사용하려고 해서 에러 발생

d = {'a':1, 'c':3}
foo(*d)        # ok a, c, 5
               # 인자로 'a', 'c'가 전달 되고, 세번째 인자는 디폴트 파라메터이므로 문제 없다.
foo(**d)      # TypeError: foo() missing 1 required positional argument: 'b'
               # a=1, c=3 형태로 전달되고 b가 빠졌으므로 에러

마치며

이상으로 파라미터 언패킹에 대해 알아 보았다. 파라미터 패킹과 언패킹은 가변 인자 함수를 만들거나, 특히 나중에 소개 될 데코레이터를 편리하게 사용하기 위해서 꼭 알아야 하는 개념이므로 완벽히 숙지하고 다음 포스트 파라미터 패킹으로 넘어 가도록 하자.

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!