30.3 키워드 인수와 딕셔너리 언패킹 사용하기

지금까지 함수를 호출할 때 키워드 인수로 직접 값을 넣었습니다. 이번에는 딕셔너리를 사용해서 키워드 인수로 값을 넣는 딕셔너리 언패킹을 사용해보겠습니다. 다음과 같이 딕셔너리 앞에 **(애스터리스크 두 개)를 붙여서 함수에 넣어줍니다.

  • 함수(**딕셔너리)

먼저 personal_info 함수를 만듭니다.

>>> def personal_info(name, age, address):
...     print('이름: ', name)
...     print('나이: ', age)
...     print('주소: ', address)
...

이제 딕셔너리에 '키워드': 값 형식으로 인수를 저장하고, 앞에 **를 붙여서 함수에 넣어줍니다. 이때 딕셔너리의 키워드(키)는 반드시 문자열 형태라야 합니다.

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**x)
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

딕셔너리에 저장된 값들이 잘 출력되었습니다. **x처럼 딕셔너리를 언패킹하면 딕셔너리의 값들이 함수의 인수로 들어갑니다. 즉, personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동') 또는 personal_info('홍길동', 30, '서울시 용산구 이촌동')과 똑같은 동작이 됩니다.

그림 30-2 딕셔너리 언패킹

딕셔너리 변수 대신 딕셔너리 앞에 바로 **를 붙여도 동작은 같습니다.

>>> personal_info(**{'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'})
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

딕셔너리 언패킹을 사용할 때는 함수의 매개변수 이름과 딕셔너리의 키 이름이 같아야 합니다. 또한, 매개변수 개수와 딕셔너리 키의 개수도 같아야 합니다.

만약 이름과 개수가 다르면 함수를 호출할 수 없습니다. 여기서는 함수를 def personal_info(name, age, address):로 만들었으므로 딕셔너리도 똑같이 맞춰주어야 합니다. 다음과 같이 매개변수 이름, 개수가 다른 딕셔너리를 넣으면 에러가 발생합니다.

>>> personal_info(**{'name': '홍길동', 'old': 30, 'address':'서울시 용산구 이촌동'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: personal_info() got an unexpected keyword argument 'old'
>>> personal_info(**{'name': '홍길동', 'age': 30})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: personal_info() missing 1 required positional argument: 'address'

30.3.1  **를 두 번 사용하는 이유

그런데 딕셔너리는 **처럼 *를 두 번 사용할까요? 왜냐하면 딕셔너리는 키-값 쌍 형태로 값이 저장되어 있기 때문입니다. 먼저 *를 한 번만 사용해서 함수를 호출해봅니다.

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(*x)
이름:  name
나이:  age
주소:  address

personal_info*x를 넣으면 x의 키가 출력됩니다. 즉, 딕셔너리를 한 번 언패킹하면 키를 사용한다는 뜻이 됩니다. 따라서 **처럼 딕셔너리를 두 번 언패킹하여 값을 사용하도록 만들어야 합니다.

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**x)
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

30.3.2  키워드 인수를 사용하는 가변 인수 함수 만들기

이번에는 키워드 인수를 사용하는 가변 인수 함수를 만들어보겠습니다. 다음과 같이 키워드 인수를 사용하는 가변 인수 함수는 매개변수 앞에 **를 붙여서 만듭니다.

def 함수이름(**매개변수):
    코드

이제 값 여러 개를 받아서 매개변수 이름과 값을 각 줄에 출력하는 함수를 만들어보겠습니다. 함수를 만들 때 괄호 안에 **kwargs와 같이 매개변수 앞에 **를 붙입니다. 함수 안에서는 forkwargs.items()를 반복하면서 print로 값을 출력합니다.

>>> def personal_info(**kwargs):
...     for kw, arg in kwargs.items():
...         print(kw, ': ', arg, sep='')
...

매개변수 이름은 원하는 대로 지어도 되지만 관례적으로 keyword arguments를 줄여서 kwargs로 사용합니다. 특히 이 kwargs는 딕셔너리라서 for로 반복할 수 있습니다.

그럼 personal_info 함수에 키워드와 값을 넣어서 실행해봅니다. 값을 한 개 넣어도 되고, 세 개 넣어도 됩니다.

>>> personal_info(name='홍길동')
name: 홍길동
>>> personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
name: 홍길동
age: 30
address: 서울시 용산구 이촌동

이렇게 인수를 직접 넣어도 되고, 딕셔너리 언패킹을 사용해도 됩니다. 다음과 같이 딕셔너리를 만들고 앞에 **를 붙여서 넣어봅니다.

>>> x = {'name': '홍길동'}
>>> personal_info(**x)
name: 홍길동
>>> y = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**y)
name: 홍길동
age: 30
address: 서울시 용산구 이촌동

딕셔너리에 들어있는 값이 그대로 출력되었습니다. 즉, 딕셔너리 x{'name': '홍길동'}이므로 personal_info(**x)로 호출하면 personal_info(name='홍길동')과 같고, 딕셔너리 y{'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}이므로 personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')과 같습니다.

이처럼 함수를 만들 때 def personal_info(**kwargs):와 같이 매개변수에 **를 붙여주면 키워드 인수를 사용하는 가변 인수 함수를 만들 수 있습니다. 그리고 이런 함수를 호출할 때는 키워드와 인수를 각각 넣거나 딕셔너리 언패킹을 사용하면 됩니다.

보통 **kwargs를 사용한 가변 인수 함수는 다음과 같이 함수 안에서 특정 키가 있는지 확인한 뒤 해당 기능을 만듭니다.

def personal_info(**kwargs):
    if 'name' in kwargs:    # in으로 딕셔너리 안에 특정 키가 있는지 확인
        print('이름: ', kwargs['name'])
    if 'age' in kwargs:
        print('나이: ', kwargs['age'])
    if 'address' in kwargs:
        print('주소: ', kwargs['address'])
참고 | 고정 인수와 가변 인수(키워드 인수)를 함께 사용하기

고정 인수와 가변 인수(키워드 인수)를 함께 사용할 때는 다음과 같이 고정 매개변수를 먼저 지정하고, 그 다음 매개변수에 **를 붙여주면 됩니다.

>>> def personal_info(name, **kwargs):
...     print(name)
...     print(kwargs)
...
>>> personal_info('홍길동')
홍길동
{}
>>> personal_info('홍길동', age=30, address='서울시 용산구 이촌동')
홍길동
{'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**{'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'})
홍길동
{'age': 30, 'address': '서울시 용산구 이촌동'}

단, 이때 def personal_info(**kwargs, name):처럼 **kwargs가 고정 매개변수보다 앞쪽에 오면 안 됩니다. 매개변수 순서에서 **kwargs는 반드시 가장 뒤쪽에 와야 합니다.

참고 | 위치 인수와 키워드 인수를 함께 사용하기

함수에서 위치 인수를 받는 *args와 키워드 인수를 받는 **kwargs를 함께 사용할 수도 있습니다. 대표적인 함수가 print인데 print는 출력할 값을 위치 인수로 넣고 sep, end 등을 키워드 인수로 넣습니다. 다음과 같이 함수의 매개변수를 *args, **kwargs로 지정하면 위치 인수와 키워드 인수를 함께 사용합니다.

>>> def custom_print(*args, **kwargs):
...     print(*args, **kwargs)
...
>>> custom_print(1, 2, 3, sep=':', end='')
1:2:3

단, 이때 def custom_print(**kwargs, *args):처럼 **kwargs*args보다 앞쪽에 오면 안 됩니다. 매개변수 순서에서 **kwargs는 반드시 가장 뒤쪽에 와야 합니다.

특히 고정 매개변수와 *args, **kwargs를 함께 사용한다면 def custom_print(a, b, *args, **kwargs):처럼 매개변수는 고정 매개변수, *args, **kwargs 순으로 지정해야 합니다.