PEP 570 -- Python Positional-Only Parameters에서는 Positional-Only Arguments, Keyword-Only Arguments 개념이 제언되었다. Accepted 상태지만 Python 3.0부터 3.7까지 Keyword-Only Arguments만이 반영되어 있다. 이건 파이썬에서 함수의 인자 형태를 구분하는 방식을 이해하고 있으면 더 알기 쉽다.

Python에서 함수의 인자 구분

함수를 정의하는 입장

함수를 정의하는 입장에서, 인자는 필수(required) 인자선택(optional) 인자로 구분할 수 있다. 두 인자의 차이는 기본값 정의 유무이며, 파이썬 인터프리터에서는 이들을 각각 non-default argument, default argument라는 어휘와 섞어 사용한다.

def range_list(start, end, step=1):
    return list(range(start, end, step))

print(range_list(0, 5)) # [0, 1, 2, 3, 4]
print(range_list(0, 5, 2)) # [0, 2, 4]

step 만큼의 공차를 가진, start부터 end-1 사이의 등차수열을 만들어 리스트로 반환하는 함수다. print문 부분을 보면 이해가 더 쉽다. 아무튼, 여기서 range_list 함수의 인자 3개를 구분해 보자.

  • start : 필수 인자(non-default argument)
  • end : 필수 인자(non-default argument)
  • step : 선택 인자(default argument)

함수를 호출할 때 필수 인자를 모두 채우지 않으면 TypeError가 발생한다.

def range_list(start, end, step=1):
    ...

range_list(1) # TypeError: range_list() missing 1 required positional argument: 'end'

여담으로, 선택 인자를 필수 인자 앞에 정의하면 SyntaxError가 발생함에 주의해야 한다.

def range_list(step=1, start, end): # SyntaxError: non-default argument follows default argument
    ...

함수를 호출하는 입장

이번 글의 핵심은 이 부분인데, 함수를 호출하는 입장에서 인자는 위치(positional) 인자키워드(keyword) 인자로 나뉜다. 함수가 인자를 정의하는 부분에 문법적인 추가 작업을 하지 않았다면, 함수 호출자는 두 가지 방식으로 인자를 전달할 수 있다.

def sum(a, b):
    return a + b

print(sum(1, 3))
# a와 b 모두 위치 인자 형태로 전달

print(sum(1, b=3))
# a는 위치 인자, b는 키워드 인자 형태로 전달

print(sum(a=3, b=3))
# a와 b 모두 키워드 인자 형태로 전달

값을 전달할 인자의 이름을 별도로 명시한다면 키워드 인자, 명시하지 않는다위치 인자라고 구분하면 된다. 일반적인 함수에 인자를 전달할 때는 위치 인자와 키워드 인자 형태를 마음대로 사용할 수 있는데, PEP 570은 이를 강제하는 문법을 제언한다.

X-Only Arguments

Positional-Only Arguments

PEP 570에서는 /,라는 문법을 통해 positional-only를 명시할 수 있도록 제안하고 있다. 실제로 Python 언어 스펙에 반영되어 있는 것은 아니라서 아래의 함수 정의문은 SyntaxError가 발생하지만, 문법적으로 가능하다고 가정한 채 예제를 작성해 보겠다.

def a(positional_only, /):
      ...

a(1) # 문제 없음
a(positional_only=1) # TypeError : (대충 이 함수는 positional argument만 받고 있다는 내용)

Keyword-Only Arguments

PEP 570에서는 *,라는 문법을 통해 keyword-only를 명시할 수 있도록 제안하고 있으며, 이는 실제로 Python 언어 스펙에 반영되어 있다. * 앞의 것들은 positional-or-keyword, 뒤의 것들은 keyword-only argument를 의미하게 된다.

def a(positional_or_keyword, *, keyword_only):
    ...

a() # TypeError: a() missing 1 required positional argument: 'positional_or_keyword'
a(1) # TypeError: a() missing 1 required keyword-only argument: 'keyword_only'
a(1, 2) # TypeError: a() takes 1 positional argument but 2 were given
a(1, keyword_only=2) # 문제 없음
a(positional_or_keyword=1, keyword_only=2) # 문제 없음

참고로, X-Only Arguments는 단지 hinting만이 아니며 추가적인 문법적 제약을 명시하는 것임을 알고 있어야 한다. 아래처럼, type hinting은 무시해도 되지만 X-Only Arguments 제약은 무시하면 에러가 발생한다.

def a(_: int):
    pass

a('') # 에러가 발생하지 않음

def b(*, _):
      pass

b() # TypeError: b() missing 1 required keyword-only argument: '_'