Keyword-only 인자 — `*,` 가 뭔지, 왜 쓰는지
`def f(a, b, *, c)` 의 그 별 하나
문법
def foo(a, b, *, c):
...
foo(1, 2, c=3) # OK
foo(1, 2, 3) # TypeError: foo() takes 2 positional arguments but 3 were given
foo(c=3, a=1, b=2) # OK
* 자체는 가짜 파라미터 같은 표시일 뿐, 받는 값은 없다. "여기부터는 keyword로만 받는다"는 구분선 역할.
*args 와의 관계
*args(가변 위치 인자)도 같은 효과를 가진다. *args 뒤의 파라미터는 자동으로 keyword-only가 된다.
def bar(a, *args, c): # c는 keyword-only
...
*args가 필요 없을 때 그냥 *만 쓰는 거라고 보면 된다.
왜 쓰는가
1. 디폴트가 없는 인자를 디폴트 뒤에 배치
def f(a=1, b=2, enable_evidence: bool): # SyntaxError
...
def f(a=1, b=2, *, enable_evidence: bool): # OK
...
디폴트 인자 뒤에 비-디폴트 인자는 위치로 못 받지만, keyword-only면 가능하다.
2. 호출 의도 명확화
process(item, filter_list, True)
# True가 뭔데? enabled? force? dry_run?
process(item, filter_list, enable_evidence=True)
# 자명
시그니처에 *,를 두면 후자만 허용된다.
3. 시그니처 변경 안전성
파라미터 순서를 바꿔도 호출 측이 안 깨진다. 키워드로 묶여 있으니까.
표준 라이브러리 예
sorted(iterable, *, key=None, reverse=False)
print(*objects, sep=' ', end='\n') # *objects 뒤는 keyword-only
sorted(list, lambda x: x)가 안 되고 sorted(list, key=lambda x: x)로 써야 하는 이유가 바로 *로 끊어놨기 때문.
Ruby와의 비교
Ruby에는 *, 같은 문법이 없다. 필요가 없기 때문이다.
def foo(a, b, c:)
# ...
end
foo(1, 2, c: 3) # OK
foo(1, 2, 3) # ArgumentError: wrong number of arguments
Ruby의 c: 자체가 처음부터 키워드 전용이다. 위치로는 못 넘긴다. Python처럼 "위치도 되고 키워드도 되는 인자"가 기본값이 아니라, 위치 인자(a, b)와 키워드 인자(c:)가 문법적으로 분리되어 있다.
| Python | Ruby | |
|---|---|---|
| 기본 인자 | 위치 + 키워드 둘 다 가능 | 위치 전용 |
| 키워드 전용 | *, 뒤에 선언 |
name: 형태로 선언 |
| 가변 위치 | *args |
*args |
| 가변 키워드 | **kwargs |
**kwargs |
Ruby가 "키워드로 받고 싶으면 처음부터 키워드로 선언하라"는 입장이라면, Python은 "같은 인자도 호출 방식을 강제할 수 있다"는 입장이다.
Ruby의 디폴트 인자 순서 문제
Python에서 *,를 쓰는 첫 번째 이유 — "디폴트 뒤에 비-디폴트" — 가 Ruby에선 어떻게 되나?
def f(a = 1, b) # OK in Ruby! (역순도 허용)
# ...
end
f(2) # a=1, b=2
f(2, 3) # a=2, b=3
Ruby는 위치 인자에서도 디폴트 위치가 자유롭다. 그리고 어차피 키워드 인자(b:)는 디폴트 유무에 관계없이 어디든 둘 수 있다. 그래서 *, 같은 우회 문법이 필요 없다.
핵심 포인트
`*,` 뒤의 모든 파라미터는 keyword로만 전달 — 위치로 넘기면 TypeError
`*` 자체는 값을 안 받는 마커 — `*args` 뒤도 동일하게 keyword-only
디폴트 인자 뒤에 비-디폴트 인자를 둬야 할 때 유일한 방법
호출 측에 의도를 강제 — `True` 같은 의미 모를 위치 인자 방지
Ruby는 `name:` 자체가 keyword-only라 동등한 문법 불필요