알림을 보낼 사람은 한 명일 수도, 여러 명일 수도 있다. 옵션도 urgent=True처럼 필요할 때만 붙인다. 이때 *args와 **kwargs를 보면 별표 개수부터 세게 된다. 두 문법의 핵심은 간단하다. 함수를 정의할 때 *는 남은 위치 인수를 모으고, **는 남은 키워드 인수를 모은다.
파이썬 가변 인수는 위치와 키워드를 어떻게 구분할까?
먼저 코드에서 이름을 args, kwargs 대신 용도가 드러나는 recipients, options로 바꿔 보자. 중요한 것은 이름보다 앞의 별표다.
도식처럼 남은 위치 인수는 튜플에, 이름이 있는 인수는 딕셔너리에 모인다. 함수 안에서 두 값을 출력해 실제 자료형과 내용을 확인해 보자.
def prepare_notice(channel, *recipients, **options):
return {
"channel": channel,
"recipients": recipients,
"options": options,
}
notice = prepare_notice("email", "Ana", "Ben", urgent=True, retries=2)
print(notice["recipients"])
print(notice["options"])('Ana', 'Ben')
{'urgent': True, 'retries': 2}첫 번째 위치 인수 "email"은 channel에 들어간다. 나머지 위치 인수 "Ana", "Ben"은 튜플로 묶여 recipients가 된다. urgent=True, retries=2는 이름이 붙은 키워드 인수라 딕셔너리로 묶여 options가 된다.
리스트와 딕셔너리를 그대로 넘기면 왜 중첩될까?
이번에는 호출하는 쪽에 이미 리스트와 딕셔너리가 있다고 해 보자. 이 둘을 그냥 인수로 넣으면 내용이 자동으로 펼쳐지지는 않는다.
users = ["Ana", "Ben"]
settings = {"urgent": True, "retries": 2}
notice = prepare_notice("email", users, settings)
print(notice["recipients"])
print(notice["options"])(['Ana', 'Ben'], {'urgent': True, 'retries': 2})
{}리스트 하나와 딕셔너리 하나가 각각 위치 인수 한 개씩 들어간 것이다. 그래서 recipients에는 두 객체를 담은 튜플이 생기고, 이름을 붙여 전달한 인수는 없으므로 options는 빈 딕셔너리다. 알림 대상이 갑자기 리스트째 한 사람이 된 듯 보인다면, 받는 함수보다 호출하는 줄을 먼저 보자.
호출할 때 별표는 리스트와 딕셔너리를 어떻게 펼칠까?
원하는 것이 리스트의 각 사람과 딕셔너리의 각 옵션을 개별 인수로 전달하는 것이라면 호출 위치에서 별표를 붙인다.
notice = prepare_notice("email", *users, **settings)
print(notice["recipients"])
print(notice["options"])('Ana', 'Ben')
{'urgent': True, 'retries': 2}같은 기호라도 함수 정의에서는 모으고, 호출에서는 펼친다. 이 차이를 기억하면 코드에서 별표가 어느 쪽에 붙었는지 먼저 볼 수 있다.
언제 가변 인수를 쓰지 않는 편이 나을까?
**options는 모르는 키워드도 받아들인다. 예를 들어 urgnet=True라고 잘못 써도 함수는 오류를 내지 않고 {'urgnet': True}를 받는다. 함수 안에서 허용된 옵션을 검사하지 않으면 오타가 조용히 지나간다.
옵션의 종류가 정해져 있다면 def prepare_notice(channel, recipients, urgent=False, retries=0)처럼 명시적인 매개변수가 더 읽기 쉽고 오타도 일찍 드러낸다. 반대로 다른 함수로 여러 인수를 전달하는 래퍼처럼 입력의 종류가 실제로 달라질 때는 *args와 **kwargs가 유용하다. 별표는 편리하지만, 함수가 무엇을 받는지 숨겨도 된다는 허가는 아니다.
핵심 요약
함수를 정의할 때 *args는 남은 위치 인수를 튜플로, **kwargs는 남은 키워드 인수를 딕셔너리로 모은다. 함수를 호출할 때 *users와 **settings는 리스트와 딕셔너리의 내용을 인수로 펼친다. 값이 예상과 다르면 정의와 호출 중 별표가 어느 쪽에 있는지 확인하고, 고정된 옵션까지 무조건 **kwargs에 감추지는 말자.

