[Final] PEP 570 - Python Positional-Only Parameters
원문 링크: PEP 570 - Python Positional-Only Parameters
상태: Final 유형: Standards Track 작성일: 20-Jan-2018
Now I have the content of PEP 0570. I will proceed with the translation and summarization according to the provided guidelines.
# PEP 570 – Python Positional-Only Parameters (위치 전용 매개변수)
## 개요 (Abstract)
이 PEP(Python Enhancement Proposal)는 Python 함수 정의에서 위치 전용(positional-only) 매개변수를 지정하기 위한 새로운 문법인 `/`를 도입할 것을 제안합니다.
위치 전용 매개변수는 외부에서 사용할 수 있는 이름이 없습니다. 위치 전용 매개변수를 받는 함수가 호출될 때, 위치 인수(positional arguments)는 오직 순서에 기반하여 이 매개변수들에 매핑됩니다.
API(Application Programming Interface)를 설계할 때, 라이브러리 작성자는 API의 올바르고 의도된 사용을 보장하려고 노력합니다. 어떤 매개변수를 위치 전용으로 지정할 수 있는 기능이 없다면, 라이브러리 작성자는 적절한 매개변수 이름을 선택하는 데 신중해야 합니다. 이는 필수 매개변수이거나 매개변수가 API 호출자에게 외부적인 의미가 없는 경우에도 마찬가지입니다.
이 PEP에서는 다음 내용을 다룹니다.
* Python에서 위치 전용 매개변수의 역사와 현재 의미
* 위치 전용 매개변수가 없을 때 발생하는 문제
* 언어 내에서 위치 전용 매개변수를 직접 지원하지 않을 때 이러한 문제들이 어떻게 처리되는지
* 위치 전용 매개변수를 가짐으로써 얻는 이점
동기(Motivation)의 맥락에서 다음을 논의합니다.
* 위치 전용 매개변수가 왜 언어 고유의 기능이어야 하는지
* 위치 전용 매개변수를 표시하기 위한 문법 제안
* 이 새로운 기능을 어떻게 가르칠지
* 거부된 아이디어들을 더 자세히 설명
## 동기 (Motivation)
### Python에서 위치 전용 매개변수 의미의 역사 (History of Positional-Only Parameter Semantics in Python)
Python은 원래 위치 전용 매개변수를 지원했습니다. 초기 버전의 언어는 인수를 이름으로 매개변수에 바인딩하여 함수를 호출하는 기능이 없었습니다. Python 1.0경에 매개변수 의미론이 위치-또는-키워드(positional-or-keyword) 방식으로 변경되었습니다. 그 이후로 사용자들은 함수의 정의에 지정된 키워드 이름을 통해 또는 위치적으로 함수에 인수를 제공할 수 있게 되었습니다.
현재 Python 버전에서는 많은 CPython "builtin" 및 표준 라이브러리 함수가 위치 전용 매개변수만 허용합니다. 이러한 의미론은 키워드 인수를 사용하여 이 함수들 중 하나를 호출함으로써 쉽게 관찰할 수 있습니다.
```python
>>> help(pow)
... pow(x, y, z=None, /)
...
>>> pow(x=5, y=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments
pow()
함수는 /
마커를 통해 매개변수가 위치 전용임을 나타냅니다. 그러나 이는 단지 문서화 관례일 뿐이며, Python 개발자는 이 문법을 코드에서 사용할 수 없었습니다.
다른 흥미로운 의미론을 가진 함수들도 있습니다.
range()
는 필수 매개변수 왼쪽에 선택적 매개변수를 허용하는 오버로드된 함수입니다.dict()
는 매핑/이터레이터 매개변수가 선택적이며 의미론적으로 위치 전용이어야 합니다. 이 매개변수에 대해 외부에서 보이는 어떤 이름이든**kwarg
키워드 가변 매개변수dict
로 들어가는 이름을 가리게 될 것입니다.
Python 코드에서 이러한 의미론을 에뮬레이션(emulate)하려면 (*args, **kwargs)
를 받아 인수를 수동으로 파싱해야 합니다. 그러나 이는 함수 정의와 함수가 계약적으로 받아들이는 것 사이에 불일치를 초래합니다. 함수 정의가 인수 처리 로직과 일치하지 않습니다.
또한, /
문법은 CPython 외에서도 유사한 의미론을 지정하는 데 사용되고 있어, 이러한 시나리오가 CPython 및 표준 라이브러리에만 국한된 것이 아님을 나타냅니다.
위치 전용 매개변수가 없을 때의 문제점 (Problems Without Positional-Only Parameters)
위치 전용 매개변수가 없으면 라이브러리 작성자와 API 사용자 모두에게 어려움이 있습니다.
라이브러리 작성자를 위한 과제 (Challenges for Library Authors)
위치-또는-키워드 매개변수(positional-or-keyword parameters)의 경우, 호출 규칙의 혼합이 항상 바람직한 것은 아닙니다. 작성자는 API를 키워드 인수로 호출하는 것을 허용하지 않음으로써 API 사용을 제한하고 싶을 수 있습니다. 이는 공개 API의 일부일 때 매개변수의 이름을 노출시킵니다. 이 접근 방식은 의미론적 의미가 이미 있는 필수 함수 매개변수(예: namedtuple(typenames, field_names, …)
)나 매개변수 이름이 진정한 외부 의미가 없을 때(예: min()
함수의 arg1
, arg2
, … 등) 특히 유용합니다. API 호출자가 키워드 인수를 사용하기 시작하면, 라이브러리 작성자는 매개변수 이름을 변경할 수 없습니다. 이는 호환성을 깨뜨리는 변경(breaking change)이 되기 때문입니다.
위치 전용 매개변수는 *args
에서 인수를 하나씩 추출하여 에뮬레이션할 수 있습니다. 그러나 이 접근 방식은 오류 발생 가능성이 높고, 앞서 언급했듯이 함수 정의와 동의어가 아닙니다. 함수의 사용법이 모호해져서 사용자가 함수가 계약적으로 어떤 매개변수를 받아들이는지 이해하기 위해 help()
, 관련 자동 생성 문서 또는 소스 코드를 살펴보아야 합니다.
API 사용자를 위한 과제 (Challenges for Users of an API)
사용자들은 위치 전용 표기법을 처음 접했을 때 놀랄 수 있습니다. 이는 최근에야 문서화되었고 Python 코드에서 사용할 수 없었기 때문에 예상되는 일입니다. 이러한 이유로 이 표기법은 현재 C로 개발된 CPython API에만 나타나는 예외적인 경우입니다. 이 표기법을 문서화하고 Python 코드에서 사용할 수 있게 하면 이러한 불일치가 해소될 것입니다.
더 나아가, 위치 전용 매개변수에 대한 현재 문서는 일관성이 없습니다.
- 일부 함수는 중첩된 대괄호로 묶어 선택적 위치 전용 매개변수 그룹을 나타냅니다.
- 일부 함수는 다양한 수의 매개변수를 가진 여러 프로토타입을 제시하여 선택적 위치 전용 매개변수 그룹을 나타냅니다.
- 일부 함수는 위 두 가지 접근 방식을 모두 사용합니다.
현재 문서가 구분하지 못하는 또 다른 점은 함수가 위치 전용 매개변수를 취하는지 여부입니다. open()
은 키워드 인수를 허용하지만, ord()
는 그렇지 않습니다. 기존 문서만으로는 이를 알 수 있는 방법이 없습니다.
위치 전용 매개변수의 이점 (Benefits of Positional-Only Parameters)
위치 전용 매개변수는 라이브러리 작성자에게 API의 의도된 사용법을 더 잘 표현하고, 안전하고 하위 호환 가능한 방식으로 API를 발전시킬 수 있는 더 많은 제어권을 제공합니다. 또한, Python 언어를 기존 문서 및 다양한 “builtin” 및 표준 라이브러리 함수의 동작과 더욱 일관성 있게 만듭니다.
라이브러리 작성자 역량 강화 (Empowering Library Authors)
라이브러리 작성자는 위치 전용 매개변수의 이름을 호출자를 깨뜨리지 않고 변경할 수 있는 유연성을 갖게 됩니다. 이러한 유연성은 필수 매개변수 또는 진정한 외부 의미가 없는 매개변수에 대해 적절한 공개용 이름을 선택하는 데 드는 인지적 부담을 줄여줍니다.
위치 전용 매개변수는 다음과 같은 여러 상황에서 유용합니다.
- 함수가 어떤 키워드 인수도 허용하지만 위치 인수도 허용할 수 있는 경우
- 매개변수가 외부적인 의미가 없는 경우
- API의 매개변수가 필수이며 함수에 대해 모호하지 않은 경우
핵심 시나리오는 함수가 어떤 키워드 인수도 허용하지만 위치 인수도 허용할 수 있는 경우입니다. 대표적인 예는 Formatter.format
및 dict.update
입니다. 예를 들어, dict.update
는 딕셔너리(위치적으로), 키/값 쌍의 이터러블(위치적으로), 또는 여러 키워드 인수를 허용합니다. 이 시나리오에서 딕셔너리 매개변수가 위치 전용이 아니라면, 사용자는 함수 정의가 매개변수에 사용하는 이름을 사용할 수 없거나, 반대로 함수는 수신된 인수가 딕셔너리/이터러블인지 또는 키/값 쌍을 업데이트하기 위한 키워드 인수인지 쉽게 구분할 수 없습니다.
위치 전용 매개변수가 유용한 또 다른 시나리오는 매개변수 이름이 진정한 외부 의미가 없을 때입니다. 예를 들어, 한 유형에서 다른 유형으로 변환하는 함수를 만들고 싶다고 가정해 봅시다.
def as_my_type(x):
...
이 매개변수의 이름은 본질적인 가치를 제공하지 않으며, 호출자가 x
를 키워드 인수로 전달할 수 있으므로 API 작성자는 이 이름을 영원히 유지해야 합니다.
또한, 위치 전용 매개변수는 API의 매개변수가 필수이며 함수에 대해 모호하지 않은 경우에 유용합니다. 예를 들어:
def add_to_queue(item: QueueItem):
...
함수 이름이 예상되는 인수를 명확히 합니다. 키워드 인수는 최소한의 이점만 제공하며 API의 미래 발전을 제한합니다. 나중에 이 함수가 하위 호환성을 유지하면서 여러 항목을 받을 수 있도록 하고 싶다고 가정해 봅시다.
def add_to_queue(items: Union[QueueItem, List[QueueItem]]):
...
또는 인수 목록을 사용하여 받을 수 있도록:
def add_to_queue(*items: QueueItem):
...
작성자는 잠재적으로 호출자를 깨뜨리는 것을 피하기 위해 항상 원래 매개변수 이름을 유지해야 할 것입니다.
위치 전용 매개변수를 지정할 수 있게 됨으로써, 작성자는 매개변수 이름을 자유롭게 변경하거나, 이전 예시에서 보았듯이 *args
로 변경할 수도 있습니다. 표준 라이브러리에는 이 범주에 속하는 여러 함수 정의가 있습니다. 예를 들어, collections.defaultdict
의 필수 매개변수(문서에서는 default_factory
라고 불림)는 위치적으로만 전달될 수 있습니다. 이 상황의 특별한 경우 중 하나는 클래스 메서드의 self
매개변수입니다. 클래스에서 메서드를 호출할 때 호출자가 self
라는 이름으로 키워드 바인딩할 수 있는 것은 바람직하지 않습니다.
io.FileIO.write(self=f, b=b"data")
실제로 C로 구현된 표준 라이브러리의 함수 정의는 일반적으로 self
를 위치 전용 매개변수로 취합니다.
>>> help(io.FileIO.write)
Help on method_descriptor:
write(self, b, /)
Write buffer b to file, return number of bytes written.
언어 일관성 향상 (Improving Language Consistency)
위치 전용 매개변수를 통해 Python 언어는 더욱 일관성을 갖게 될 것입니다. 이 개념이 확장 모듈에만 국한된 기능이 아니라 Python의 일반적인 기능이 된다면, 위치 전용 매개변수를 가진 함수를 접하는 사용자들의 혼란을 줄일 수 있습니다. 일부 주요 서드파티 패키지들은 이미 함수 정의에서 /
표기법을 사용하고 있습니다.
위치 전용 매개변수를 지정하는 “builtin” 함수와 위치 문법이 부족한 순수 Python 구현 사이의 간극을 좁히는 것은 일관성을 향상시킬 것입니다. /
문법은 argument clinic
에 의해 builtins와 인터페이스가 생성될 때와 같이 기존 문서에서 이미 노출되어 있습니다.
고려해야 할 또 다른 중요한 측면은 PEP 399입니다. 이는 표준 라이브러리의 순수 Python 버전 모듈이 C로 구현된 액셀러레이터(accelerator) 모듈과 동일한 인터페이스와 의미론을 가져야 한다고 요구합니다. 예를 들어, collections.defaultdict
가 순수 Python 구현을 갖게 된다면, C 구현의 인터페이스와 일치하기 위해 위치 전용 매개변수를 사용해야 할 것입니다.
근거 (Rationale)
우리는 Python 언어에 위치 전용 매개변수를 새로운 문법으로 도입할 것을 제안합니다.
새로운 문법은 라이브러리 작성자가 API가 호출될 방식을 더 효과적으로 제어할 수 있도록 할 것입니다. 어떤 매개변수가 위치 전용으로 호출되어야 하는지 지정하는 동시에, 키워드 인수로 호출되는 것을 방지할 수 있게 됩니다.
이전에는 (정보성) PEP 457이 문법을 정의했지만, 훨씬 더 모호한 범위로 정의했습니다. 이 PEP는 원래 제안을 한 단계 더 발전시켜 문법을 정당화하고 함수 정의에서 /
문법에 대한 구현을 제공합니다.
성능 (Performance)
앞서 언급된 이점 외에도, 위치 전용 인수의 파싱(parsing) 및 처리가 더 빠릅니다. 이러한 성능 이점은 키워드 인수를 위치 인수로 변환하는 것에 대한 이 스레드에서 시연될 수 있습니다. 이러한 속도 향상 때문에 최근에는 내장 함수(builtins)에서 키워드 인수를 멀리하는 추세가 있었습니다. 최근에는 bool
, float
, list
, int
, tuple
에 대한 키워드 인수를 허용하지 않도록 하위 호환성이 없는 변경이 이루어졌습니다.
유지보수성 (Maintainability)
Python에서 위치 전용 매개변수를 지정하는 방법을 제공함으로써 C 모듈의 순수 Python 구현을 유지보수하기가 더 쉬워질 것입니다. 또한, 함수를 정의하는 라이브러리 작성자는 키워드 인수를 전달하는 것이 추가적인 명확성을 제공하지 않는다고 판단될 경우 위치 전용 매개변수를 선택할 수 있습니다.
이것은 Python 메일링 리스트에서 잘 논의되고 반복되는 주제입니다.
논리적 순서 (Logical ordering)
위치 전용 매개변수는 이를 사용하는 인터페이스를 호출할 때 어떤 논리적 순서를 강제하는 (사소한) 이점도 있습니다. 예를 들어, range
함수는 모든 매개변수를 위치적으로 받으며 다음과 같은 형태는 허용하지 않습니다.
range(stop=5, start=0, step=2)
range(stop=5, step=2, start=0)
range(step=2, start=0, stop=5)
range(step=2, stop=5, start=0)
이는 (유일하게) 의도된 순서에 대해 키워드 인수를 사용하는 것을 허용하지 않는다는 대가로 이루어집니다.
range(start=0, stop=5, step=2)
순수 Python 및 C 모듈을 위한 호환성 (Compatibility for Pure Python and C Modules)
위치 전용 매개변수의 또 다른 중요한 동기는 PEP 399: 순수 Python/C 액셀러레이터 모듈 호환성 요구사항입니다. 이 PEP는 다음과 같이 명시합니다.
이 PEP는 C 코드가 순수 Python 코드에 사용되는 테스트 스위트를 통과하여 합리적으로 가능한 한 완벽한 대체품 역할을 해야 한다고 요구합니다.
C 코드가 argument clinic
및 관련 도구를 사용하여 위치 전용 매개변수를 구현하기 위한 기존 기능을 사용하여 구현된 경우, 순수 Python 대응물이 제공된 인터페이스 및 요구 사항과 일치하는 것은 불가능합니다. 이는 CPython 표준 라이브러리의 일부 함수 및 클래스와 다른 Python 구현 간에 인터페이스 불일치를 초래합니다. 예를 들어:
$ python3 # CPython 3.7.2
>>> import binascii; binascii.crc32(data=b'data')
TypeError: crc32() takes no keyword arguments
$ pypy3 # PyPy 6.0.0
>>>> import binascii; binascii.crc32(data=b'data')
2918445923
다른 Python 구현은 CPython API를 수동으로 재현할 수 있지만, 이는 Python 표준 라이브러리에 추가되는 모든 모듈이 동일한 인터페이스와 의미론을 가진 순수 Python 구현을 가져야 한다고 의무화하여 노력의 중복을 피하려는 PEP 399의 정신에 어긋납니다.
서브클래스에서의 일관성 (Consistency in Subclasses)
위치 전용 매개변수가 이점을 제공하는 또 다른 시나리오는 서브클래스가 기본 클래스의 메서드를 오버라이드(override)하고 위치적이어야 하는 매개변수의 이름을 변경할 때 발생합니다.
class Base:
def meth(self, arg: int) -> str: ...
class Sub(Base):
def meth(self, other_arg: int) -> str: ...
def func(x: Base):
x.meth(arg=12)
func(Sub()) # 런타임 오류
이 상황은 리스코프 치환 원칙(Liskov Substitution Principle) 위반으로 간주될 수 있습니다. 서브클래스는 기본 클래스의 인스턴스가 예상되는 컨텍스트에서 사용될 수 없습니다. 메서드를 오버로드할 때 인수의 이름을 변경하는 것은 서브클래스가 해당 서브클래스의 특정 도메인에 더 적합한 다른 매개변수 이름을 사용해야 하는 이유가 있을 때 발생할 수 있습니다(예: Mapping
을 서브클래스하여 DNS 조회 캐시를 구현할 때, 파생 클래스는 일반적인 인수 이름 ‘key’와 ‘value’ 대신 ‘host’와 ‘address’를 사용하고 싶을 수 있습니다). 이러한 함수 정의를 위치 전용 매개변수와 함께 사용하면 사용자가 키워드 인수를 사용하여 인터페이스를 호출할 수 없으므로 이 문제를 피할 수 있습니다. 일반적으로 서브클래스를 위한 설계는 아직 작성되지 않았고 작성자가 제어할 수 없는 코드를 예상하는 것을 포함합니다. 하위 호환 가능한 방식으로 인터페이스의 진화를 용이하게 할 수 있는 조치를 갖는 것은 라이브러리 작성자에게 유용할 것입니다.
최적화 (Optimizations)
위치 전용 매개변수를 지지하는 마지막 주장은 매개변수가 엄격한 순서로 전달될 것으로 예상되기 때문에 argument clinic
에 이미 존재하는 것과 같은 일부 새로운 최적화를 허용한다는 것입니다. 예를 들어, CPython의 내부 METH_FASTCALL
호출 규약은 최근에 위치 전용 매개변수를 가진 함수에 대해 빈 키워드를 처리하는 비용을 제거하기 위해 특화되었습니다. 위치 전용 매개변수 덕분에 Python 함수의 평가 프레임을 생성할 때 유사한 성능 개선을 적용할 수 있습니다.
명세 (Specification)
문법 및 의미론 (Syntax and Semantics)
*args
와 **kwargs
를 생략하여 설명하자면, 함수 정의를 위한 문법은 다음과 같습니다.
def name(positional_or_keyword_parameters, *, keyword_only_parameters):
이 예제를 바탕으로, 함수 정의를 위한 새로운 문법은 다음과 같습니다.
def name(positional_only_parameters, /, positional_or_keyword_parameters, *, keyword_only_parameters):
다음과 같은 규칙이 적용됩니다.
/
왼쪽에 있는 모든 매개변수는 위치 전용으로 처리됩니다.- 함수 정의에
/
가 지정되지 않으면, 해당 함수는 위치 전용 인수를 허용하지 않습니다. - 위치 전용 매개변수에 대한 선택적 값(optional values)의 로직은 위치-또는-키워드 매개변수와 동일하게 유지됩니다.
- 위치 전용 매개변수가 기본값(default)과 함께 지정되면, 다음 위치 전용 및 위치-또는-키워드 매개변수도 기본값을 가져야 합니다.
- 기본값이 없는 위치 전용 매개변수는 필수 위치 전용 매개변수입니다.
따라서 다음은 유효한 함수 정의입니다.
def name(p1, p2, /, p_or_kw, *, kw):
def name(p1, p2=None, /, p_or_kw=None, *, kw):
def name(p1, p2=None, /, *, kw):
def name(p1, p2=None, /):
def name(p1, p2, /, p_or_kw):
def name(p1, p2, /):
오늘날과 마찬가지로 다음은 유효한 함수 정의입니다.
def name(p_or_kw, *, kw):
def name(*, kw):
반면에 다음은 유효하지 않습니다.
def name(p1, p2=None, /, p_or_kw, *, kw): # p_or_kw가 기본값 없이 p2=None 뒤에 올 수 없음
def name(p1=None, p2, /, p_or_kw=None, *, kw): # p1=None 뒤에 p2가 기본값 없이 올 수 없음
def name(p1=None, p2, /): # p1=None 뒤에 p2가 기본값 없이 올 수 없음
의미론적 특수 사례 (Semantic Corner Case)
다음은 이 명세의 흥미로운 결과입니다. 이 함수 정의를 고려해 봅시다.
def foo(name, **kwds):
return 'name' in kwds
이것이 True
를 반환하게 만드는 호출은 불가능합니다. 예를 들어:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
하지만 /
를 사용하면 이를 지원할 수 있습니다.
def foo(name, /, **kwds):
return 'name' in kwds
이제 위 호출은 True
를 반환합니다.
즉, 위치 전용 매개변수의 이름은 모호함 없이 **kwds
에서 사용될 수 있습니다. (다른 예로, dict()
와 dict.update()
의 시그니처에 이점이 있습니다.)
구분자로서의 “/”의 유래 (Origin of “/” as a Separator)
/
를 구분자로 사용하는 것은 2012년에 Guido van Rossum에 의해 처음 제안되었습니다.
대안적인 제안: ‘/’는 어떻습니까? 이는 “키워드 인수”를 의미하는 ‘*‘와는 일종의 반대이며, ‘/’는 새로운 문자가 아닙니다.
교육 방법 (How To Teach This)
위치 전용 매개변수를 표시하기 위한 전용 문법을 도입하는 것은 기존의 키워드 전용 인수(keyword-only arguments)와 매우 유사합니다. 이러한 개념들을 함께 가르치는 것이 사용자가 접하거나 설계할 수 있는 가능한 함수 정의를 가르치는 방법을 단순화할 수 있습니다.
이 PEP는 Python 문서의 “함수 정의에 대한 추가 정보(More on Defining Functions)” 섹션에 새로운 하위 섹션을 추가할 것을 권장합니다. 여기서 나머지 인수 유형들이 논의됩니다. 다음 단락들은 이러한 추가 사항의 초안 역할을 합니다. 이들은 위치 전용 및 키워드 전용 매개변수 모두에 대한 표기법을 소개할 것입니다. 이는 포괄적이지도 않으며, 문서에 통합될 최종 버전으로 간주되어서도 안 됩니다.
기본적으로 인수는 Python 함수에 위치적으로 또는 명시적으로 키워드로 전달될 수 있습니다. 가독성과 성능을 위해 인수가 전달되는 방식을 제한하여 개발자가 항목이 위치적으로, 위치적으로 또는 키워드로, 또는 키워드로 전달되는지 여부를 결정하기 위해 함수 정의만 보면 되도록 하는 것이 합리적입니다.
함수 정의는 다음과 같을 수 있습니다.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| |
--- Positional only --- Keyword only
여기서 /
와 *
는 선택 사항입니다. 사용되는 경우, 이 기호들은 인수가 함수에 전달되는 방식에 따라 매개변수의 종류를 나타냅니다: 위치 전용, 위치-또는-키워드, 그리고 키워드 전용. 키워드 매개변수는 이름 있는 매개변수(named parameters)라고도 불립니다.
위치-또는-키워드 인수 (Positional-or-Keyword Arguments)
함수 정의에 /
와 *
가 없으면, 인수는 함수에 위치적으로 또는 키워드로 전달될 수 있습니다.
위치 전용 매개변수 (Positional-Only Parameters)
조금 더 자세히 살펴보면, 특정 매개변수를 위치 전용으로 표시하는 것이 가능합니다. 위치 전용인 경우, 매개변수의 순서가 중요하며, 매개변수는 키워드로 전달될 수 없습니다. 위치 전용 매개변수는 /
(슬래시) 앞에 위치합니다. /
는 위치 전용 매개변수를 나머지 매개변수로부터 논리적으로 분리하는 데 사용됩니다. 함수 정의에 /
가 없으면, 위치 전용 매개변수는 없습니다.
/
뒤에 오는 매개변수는 위치-또는-키워드이거나 키워드 전용일 수 있습니다.
키워드 전용 인수 (Keyword-Only Arguments)
매개변수를 키워드 전용으로 표시하려면, 즉 매개변수가 키워드 인수로 전달되어야 함을 나타내려면, 첫 번째 키워드 전용 매개변수 바로 앞에 인수 목록에 *
를 배치합니다.
함수 예제 (Function Examples)
/
및 *
마커에 주의하면서 다음 예제 함수 정의를 고려해 봅시다.
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
가장 익숙한 형태인 첫 번째 함수 정의 standard_arg
는 호출 규칙에 제한을 두지 않으며, 인수는 위치적으로 또는 키워드로 전달될 수 있습니다.
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
두 번째 함수 pos_only_arg
는 함수 정의에 /
가 있으므로 위치 매개변수만 사용하도록 제한됩니다.
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
세 번째 함수 kwd_only_args
는 함수 정의에 *
로 표시되어 키워드 인수만 허용합니다.
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
마지막 함수는 동일한 함수 정의에서 세 가지 호출 규칙을 모두 사용합니다.
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
요약 (Recap)
함수 정의에서 어떤 매개변수를 사용할지는 사용 사례에 따라 결정됩니다.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
가이드라인은 다음과 같습니다.
- 이름이 중요하지 않거나 의미가 없고, 항상 동일한 순서로 전달될 소수의 인수만 있을 때 위치 전용(positional-only)을 사용하세요.
- 이름이 의미가 있고 함수 정의가 이름으로 명시될 때 더 이해하기 쉬울 경우 키워드 전용(keyword-only)을 사용하세요.
참조 구현 (Reference Implementation)
CPython 테스트 스위트를 통과하는 초기 구현은 평가를 위해 제공됩니다.
이 구현의 이점은 위치 전용 매개변수 처리 속도, 키워드 전용 매개변수 구현(PEP 3102)과의 일관성, 그리고 이 변경의 영향을 받는 모든 도구 및 모듈의 더 간단한 구현입니다.
거부된 아이디어 (Rejected Ideas)
아무것도 하지 않기 (Do Nothing)
항상 선택 사항입니다. 즉, 현상 유지입니다. 이것이 고려되었지만, 앞서 언급된 이점들은 언어에 추가할 가치가 있습니다.
데코레이터 (Decorators)
이 기능을 위해 Python으로 작성된 데코레이터를 제공하는 것이 python-ideas
에서 제안되었습니다.
이 접근 방식은 함수 정의를 추가 문법으로 오염시키지 않는 이점이 있습니다. 그러나 우리는 다음 이유로 이 아이디어를 거부하기로 결정했습니다.
- 매개변수 동작이 선언되는 방식에 비대칭성을 도입합니다.
- 정적 분석기(static analyzers)와 타입 체커(type checkers)가 위치 전용 매개변수를 안전하게 식별하기 어렵게 만듭니다. 키워드 전용 매개변수는 AST에 직접 노출되는 반면, 이들은 데코레이터 목록을 AST에서 쿼리하고 이름이나 추가 휴리스틱으로 올바른 것을 식별해야 합니다.
- 도구가 위치 전용 매개변수를 올바르게 식별하려면 데코레이터가 설정하는 메타데이터에 접근하기 위해 모듈을 실행해야 합니다. 선언 오류는 런타임에만 보고됩니다.
- 긴 함수 정의에서 위치 전용 매개변수를 식별하기가 더 어려울 수 있습니다. 사용자가 데코레이터의 영향을 받는 마지막 매개변수가 무엇인지 알기 위해 매개변수를 세야 하기 때문입니다.
/
문법은 C 함수에 이미 도입되었습니다. 이 불일치는argument clinic
,inspect
모듈,ast
모듈을 포함하되 이에 국한되지 않는, 이 문법을 다루는 모든 도구 및 모듈을 구현하는 것을 더 어렵게 만들 것입니다.- 데코레이터 구현은 특히 인터프리터에 직접 지원을 추가하는 것과 비교할 때 런타임 성능 비용을 부과할 가능성이 있습니다.
인수별 마커 (Per-Argument Marker)
인수별 마커는 또 다른 언어 고유 옵션입니다. 이 접근 방식은 각 매개변수에 토큰을 추가하여 위치 전용임을 나타내고, 해당 매개변수들이 함께 배치되도록 요구합니다. 예시:
def (.arg1, .arg2, arg3):
.arg1
과 .arg2
에 붙은 점(.)에 주목하십시오. 이 접근 방식은 읽기 쉬울 수 있지만, 명시적인 마커인 /
가 키워드 전용 인수(*
)와 일치하고 오류 발생 가능성이 적기 때문에 거부되었습니다.
일부 라이브러리들은 이미 밑줄 접두사(_
)를 사용하여 매개변수가 위치 전용임을 관례적으로 나타냅니다.
인수별 마커로 “” 사용 (Using “” as a Per-Argument Marker)
일부 라이브러리와 애플리케이션(예: mypy
또는 jinja
)은 이중 밑줄(__
) 접두사를 붙인 이름을 위치 전용 매개변수를 나타내는 관례로 사용합니다. 우리는 __
를 새로운 문법으로 도입하는 아이디어를 다음 이유로 거부했습니다.
- 이는 하위 호환성이 없는 변경입니다.
- 키워드 전용 매개변수가 현재 선언되는 방식과 대칭적이지 않습니다.
- 위치 전용 매개변수를 위해 AST를 쿼리하려면 일반 인수를 확인하고 이름을 검사해야 하는 반면, 키워드 전용 매개변수에는
FunctionDef.args.kwonlyargs
와 같은 속성이 연결되어 있습니다. 위치 전용 인수가 언제 끝나는지 알기 위해 모든 매개변수를 검사해야 합니다. - 마커가 더 장황하여 모든 위치 전용 매개변수를 표시해야 합니다.
- 클래스에서 이름 왜곡(name mangling)을 호출하는 것과 같은 이중 밑줄 접두사의 다른 용도와 충돌합니다.
괄호로 위치 전용 매개변수 그룹화 (Group Positional-Only Parameters With Parentheses)
튜플 매개변수 언패킹(Tuple parameter unpacking)은 함수 정의에서 튜플을 매개변수로 사용할 수 있게 하는 Python 2의 기능입니다. 이는 시퀀스 인수가 자동으로 언패킹되도록 허용합니다. 예시는 다음과 같습니다.
def fxn(a, (b, c), d): pass
튜플 인수 언패킹은 Python 3에서 제거되었습니다(PEP 3113). 이 문법을 재사용하여 위치 전용 매개변수를 구현하자는 제안이 있었습니다. 우리는 여러 가지 이유로 위치 전용 매개변수를 나타내는 이 문법을 거부했습니다.
- 이 문법은 키워드 전용 매개변수가 선언되는 방식에 대해 비대칭적입니다.
- Python 2에서 이 문법을 사용했기 때문에 이 문법의 동작에 대한 혼란을 야기할 수 있습니다. 이는 이 기능을 사용하던 Python 2 코드베이스를 포팅하는 사용자들에게 놀라움을 줄 것입니다.
- 이 문법은 튜플 리터럴과 매우 유사합니다. 이는 튜플 선언과 혼동될 수 있어 추가적인 혼란을 야기할 수 있습니다.
구분자 뒤 제안 (After Separator Proposal)
/
뒤에 위치 매개변수를 표시하는 것은 고려되었던 또 다른 아이디어였습니다. 그러나 마커 뒤의 인수를 수정하는 접근 방식을 찾을 수 없었습니다. 그렇지 않으면 마커 앞의 매개변수도 위치 전용이어야 합니다. 예를 들어:
def (x, y, /, z):
만약 /
가 z
를 위치 전용으로 표시한다고 정의하면, x
와 y
를 키워드 인수로 지정하는 것은 불가능합니다. 현재 키워드 인수가 위치 인수 뒤에 올 수 없다는 점을 고려할 때, 이 제한을 해결할 방법을 찾는 것은 혼란을 가중시킬 것입니다. 따라서 /
는 선행 매개변수와 후행 매개변수 모두를 위치 전용으로 만들 것입니다.
The user wants me to translate PEP 0570, “Python Positional-Only Parameters” into Korean. I need to make sure the translation is accurate, maintains professionalism, uses appropriate Korean terminology (or keeps English if more familiar), avoids translating code keywords/variable names, and is well-structured with Markdown.
I will go through each section of the PEP and translate it, paying close attention to the specified guidelines.
Plan:
- Abstract: Translate the abstract section.
- Motivation: Translate the motivation section, including historical context, problems, and benefits.
- Rationale: Translate the rationale section, covering performance, maintainability, logical ordering, compatibility, consistency in subclasses, and optimizations.
- Specification: Translate the syntax and semantics, grammar, and corner cases.
- How To Teach This: Translate the teaching approach and examples.
- Recap: Translate the recap for usage guidance.
- Reference Implementation: Translate this brief section.
- Rejected Ideas: Translate the section on rejected alternative approaches.
- Ensure all sentences referring to the browsed content end with a citation ``.
I will now start the translation process.# PEP 570 – Python 위치 전용 매개변수 (Positional-Only Parameters)
개요 (Abstract)
이 PEP는 Python 함수 정의에서 위치 전용(positional-only) 매개변수를 지정하기 위한 새로운 문법인 /
를 도입할 것을 제안합니다.
위치 전용 매개변수는 외부에서 사용할 수 있는 이름이 없습니다. 위치 전용 매개변수를 받는 함수가 호출될 때, 위치 인수(positional arguments)는 오직 순서에 기반하여 이 매개변수들에 매핑됩니다.
API(Application Programming Interface)를 설계할 때, 라이브러리 작성자는 API의 올바르고 의도된 사용을 보장하려고 노력합니다. 어떤 매개변수를 위치 전용으로 지정할 수 있는 기능이 없다면, 라이브러리 작성자는 적절한 매개변수 이름을 선택하는 데 신중해야 합니다. 이는 필수 매개변수이거나 매개변수가 API 호출자에게 외부적인 의미가 없는 경우에도 마찬가지입니다.
이 PEP에서는 다음 내용을 다룹니다.
- Python에서 위치 전용 매개변수의 역사와 현재 의미
- 위치 전용 매개변수가 없을 때 발생하는 문제
- 언어 내에서 위치 전용 매개변수를 직접 지원하지 않을 때 이러한 문제들이 어떻게 처리되는지
- 위치 전용 매개변수를 가짐으로써 얻는 이점
동기(Motivation)의 맥락에서 다음을 논의합니다.
- 위치 전용 매개변수가 왜 언어 고유의 기능이어야 하는지
- 위치 전용 매개변수를 표시하기 위한 문법 제안
- 이 새로운 기능을 어떻게 가르칠지
- 거부된 아이디어들을 더 자세히 설명
동기 (Motivation)
Python에서 위치 전용 매개변수 의미의 역사 (History of Positional-Only Parameter Semantics in Python)
Python은 원래 위치 전용 매개변수를 지원했습니다. 초기 버전의 언어는 인수를 이름으로 매개변수에 바인딩하여 함수를 호출하는 기능이 없었습니다. Python 1.0경에 매개변수 의미론이 위치-또는-키워드(positional-or-keyword) 방식으로 변경되었습니다. 그 이후로 사용자들은 함수의 정의에 지정된 키워드 이름을 통해 또는 위치적으로 함수에 인수를 제공할 수 있게 되었습니다.
현재 Python 버전에서는 많은 CPython “builtin” 및 표준 라이브러리 함수가 위치 전용 매개변수만 허용합니다. 이러한 의미론은 키워드 인수를 사용하여 이 함수들 중 하나를 호출함으로써 쉽게 관찰할 수 있습니다.
>>> help(pow)
... pow(x, y, z=None, /)
...
>>> pow(x=5, y=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments
pow()
함수는 /
마커를 통해 매개변수가 위치 전용임을 나타냅니다. 그러나 이는 단지 문서화 관례일 뿐이며, Python 개발자는 이 문법을 코드에서 사용할 수 없었습니다.
다른 흥미로운 의미론을 가진 함수들도 있습니다.
range()
는 필수 매개변수 왼쪽에 선택적 매개변수를 허용하는 오버로드된 함수입니다.dict()
는 매핑/이터레이터 매개변수가 선택적이며 의미론적으로 위치 전용이어야 합니다. 이 매개변수에 대해 외부에서 보이는 어떤 이름이든**kwarg
키워드 가변 매개변수dict
로 들어가는 이름을 가리게 될 것입니다.
Python 코드에서 이러한 의미론을 에뮬레이션(emulate)하려면 (*args, **kwargs)
를 받아 인수를 수동으로 파싱해야 합니다. 그러나 이는 함수 정의와 함수가 계약적으로 받아들이는 것 사이에 불일치를 초래합니다. 함수 정의가 인수 처리 로직과 일치하지 않습니다.
또한, /
문법은 CPython 외에서도 유사한 의미론을 지정하는 데 사용되고 있어, 이러한 시나리오가 CPython 및 표준 라이브러리에만 국한된 것이 아님을 나타냅니다.
위치 전용 매개변수가 없을 때의 문제점 (Problems Without Positional-Only Parameters)
위치 전용 매개변수가 없으면 라이브러리 작성자와 API 사용자 모두에게 어려움이 있습니다.
라이브러리 작성자를 위한 과제 (Challenges for Library Authors)
위치-또는-키워드 매개변수(positional-or-keyword parameters)의 경우, 호출 규칙의 혼합이 항상 바람직한 것은 아닙니다. 작성자는 API를 키워드 인수로 호출하는 것을 허용하지 않음으로써 API 사용을 제한하고 싶을 수 있습니다. 이는 공개 API의 일부일 때 매개변수의 이름을 노출시킵니다. 이 접근 방식은 의미론적 의미가 이미 있는 필수 함수 매개변수(예: namedtuple(typenames, field_names, …)
)나 매개변수 이름이 진정한 외부 의미가 없을 때(예: min()
함수의 arg1
, arg2
, … 등) 특히 유용합니다. API 호출자가 키워드 인수를 사용하기 시작하면, 라이브러리 작성자는 매개변수 이름을 변경할 수 없습니다. 이는 호환성을 깨뜨리는 변경(breaking change)이 되기 때문입니다.
위치 전용 매개변수는 *args
에서 인수를 하나씩 추출하여 에뮬레이션할 수 있습니다. 그러나 이 접근 방식은 오류 발생 가능성이 높고, 앞서 언급했듯이 함수 정의와 동의어가 아닙니다. 함수의 사용법이 모호해져서 사용자가 함수가 계약적으로 어떤 매개변수를 받아들이는지 이해하기 위해 help()
, 관련 자동 생성 문서 또는 소스 코드를 살펴보아야 합니다.
API 사용자를 위한 과제 (Challenges for Users of an API)
사용자들은 위치 전용 표기법을 처음 접했을 때 놀랄 수 있습니다. 이는 최근에야 문서화되었고 Python 코드에서 사용할 수 없었기 때문에 예상되는 일입니다. 이러한 이유로 이 표기법은 현재 C로 개발된 CPython API에만 나타나는 예외적인 경우입니다. 이 표기법을 문서화하고 Python 코드에서 사용할 수 있게 하면 이러한 불일치가 해소될 것입니다.
더 나아가, 위치 전용 매개변수에 대한 현재 문서는 일관성이 없습니다.
- 일부 함수는 중첩된 대괄호로 묶어 선택적 위치 전용 매개변수 그룹을 나타냅니다.
- 일부 함수는 다양한 수의 매개변수를 가진 여러 프로토타입을 제시하여 선택적 위치 전용 매개변수 그룹을 나타냅니다.
- 일부 함수는 위 두 가지 접근 방식을 모두 사용합니다.
현재 문서가 구분하지 못하는 또 다른 점은 함수가 위치 전용 매개변수를 취하는지 여부입니다. open()
은 키워드 인수를 허용하지만, ord()
는 그렇지 않습니다. 기존 문서만으로는 이를 알 수 있는 방법이 없습니다.
위치 전용 매개변수의 이점 (Benefits of Positional-Only Parameters)
위치 전용 매개변수는 라이브러리 작성자에게 API의 의도된 사용법을 더 잘 표현하고, 안전하고 하위 호환 가능한 방식으로 API를 발전시킬 수 있는 더 많은 제어권을 제공합니다. 또한, Python 언어를 기존 문서 및 다양한 “builtin” 및 표준 라이브러리 함수의 동작과 더욱 일관성 있게 만듭니다.
라이브러리 작성자 역량 강화 (Empowering Library Authors)
라이브러리 작성자는 위치 전용 매개변수의 이름을 호출자를 깨뜨리지 않고 변경할 수 있는 유연성을 갖게 됩니다. 이러한 유연성은 필수 매개변수 또는 진정한 외부 의미가 없는 매개변수에 대해 적절한 공개용 이름을 선택하는 데 드는 인지적 부담을 줄여줍니다.
위치 전용 매개변수는 다음과 같은 여러 상황에서 유용합니다.
- 함수가 어떤 키워드 인수도 허용하지만 위치 인수도 허용할 수 있는 경우
- 매개변수가 외부적인 의미가 없는 경우
- API의 매개변수가 필수이며 함수에 대해 모호하지 않은 경우
핵심 시나리오는 함수가 어떤 키워드 인수도 허용하지만 위치 인수도 허용할 수 있는 경우입니다. 대표적인 예는 Formatter.format
및 dict.update
입니다. 예를 들어, dict.update
는 딕셔너리(위치적으로), 키/값 쌍의 이터러블(위치적으로), 또는 여러 키워드 인수를 허용합니다. 이 시나리오에서 딕셔너리 매개변수가 위치 전용이 아니라면, 사용자는 함수 정의가 매개변수에 사용하는 이름을 사용할 수 없거나, 반대로 함수는 수신된 인수가 딕셔너리/이터러블인지 또는 키/값 쌍을 업데이트하기 위한 키워드 인수인지 쉽게 구분할 수 없습니다.
또 다른 시나리오로, 매개변수 이름이 외부적으로 의미가 없을 때 위치 전용 매개변수가 유용합니다. 예를 들어, 한 유형에서 다른 유형으로 변환하는 함수를 만들고 싶다고 가정해 봅시다.
def as_my_type(x):
...
이 매개변수의 이름은 본질적인 가치를 제공하지 않으며, 호출자가 x
를 키워드 인수로 전달할 수 있으므로 API 작성자는 이 이름을 영원히 유지해야 합니다.
또한, 위치 전용 매개변수는 API의 매개변수가 필수이며 함수에 대해 모호하지 않은 경우에 유용합니다. 예를 들어:
def add_to_queue(item: QueueItem):
...
함수 이름이 예상되는 인수를 명확히 합니다. 키워드 인수는 최소한의 이점만 제공하며 API의 미래 발전을 제한합니다. 나중에 이 함수가 하위 호환성을 유지하면서 여러 항목을 받을 수 있도록 하고 싶다고 가정해 봅시다.
def add_to_queue(items: Union[QueueItem, List[QueueItem]]):
...
또는 인수 목록을 사용하여 받을 수 있도록:
def add_to_queue(*items: QueueItem):
...
작성자는 잠재적으로 호출자를 깨뜨리는 것을 피하기 위해 항상 원래 매개변수 이름을 유지해야 할 것입니다.
위치 전용 매개변수를 지정할 수 있게 됨으로써, 작성자는 매개변수 이름을 자유롭게 변경하거나, 이전 예시에서 보았듯이 *args
로 변경할 수도 있습니다. 표준 라이브러리에는 이 범주에 속하는 여러 함수 정의가 있습니다. 예를 들어, collections.defaultdict
의 필수 매개변수(문서에서는 default_factory
라고 불림)는 위치적으로만 전달될 수 있습니다. 이 상황의 특별한 경우 중 하나는 클래스 메서드의 self
매개변수입니다. 클래스에서 메서드를 호출할 때 호출자가 self
라는 이름으로 키워드 바인딩할 수 있는 것은 바람직하지 않습니다.
io.FileIO.write(self=f, b=b"data")
실제로 C로 구현된 표준 라이브러리의 함수 정의는 일반적으로 self
를 위치 전용 매개변수로 취합니다.
>>> help(io.FileIO.write)
Help on method_descriptor:
write(self, b, /)
Write buffer b to file, return number of bytes written.
언어 일관성 향상 (Improving Language Consistency)
위치 전용 매개변수를 통해 Python 언어는 더욱 일관성을 갖게 될 것입니다. 이 개념이 확장 모듈에만 국한된 기능이 아니라 Python의 일반적인 기능이 된다면, 위치 전용 매개변수를 가진 함수를 접하는 사용자들의 혼란을 줄일 수 있습니다. 일부 주요 서드파티 패키지들은 이미 함수 정의에서 /
표기법을 사용하고 있습니다.
위치 전용 매개변수를 지정하는 “builtin” 함수와 위치 문법이 부족한 순수 Python 구현 사이의 간극을 좁히는 것은 일관성을 향상시킬 것입니다. /
문법은 argument clinic
에 의해 builtins와 인터페이스가 생성될 때와 같이 기존 문서에서 이미 노출되어 있습니다.
고려해야 할 또 다른 중요한 측면은 PEP 399입니다. 이는 표준 라이브러리의 순수 Python 버전 모듈이 C로 구현된 액셀러레이터(accelerator) 모듈과 동일한 인터페이스와 의미론을 가져야 한다고 요구합니다. 예를 들어, collections.defaultdict
가 순수 Python 구현을 갖게 된다면, C 구현의 인터페이스와 일치하기 위해 위치 전용 매개변수를 사용해야 할 것입니다.
근거 (Rationale)
우리는 Python 언어에 위치 전용 매개변수를 새로운 문법으로 도입할 것을 제안합니다.
새로운 문법은 라이브러리 작성자가 API가 호출될 방식을 더 효과적으로 제어할 수 있도록 할 것입니다. 어떤 매개변수가 위치 전용으로 호출되어야 하는지 지정하는 동시에, 키워드 인수로 호출되는 것을 방지할 수 있게 됩니다.
이전에는 (정보성) PEP 457이 문법을 정의했지만, 훨씬 더 모호한 범위로 정의했습니다. 이 PEP는 원래 제안을 한 단계 더 발전시켜 문법을 정당화하고 함수 정의에서 /
문법에 대한 구현을 제공합니다.
성능 (Performance)
앞서 언급된 이점 외에도, 위치 전용 인수의 파싱(parsing) 및 처리가 더 빠릅니다. 이러한 성능 이점은 키워드 인수를 위치 인수로 변환하는 것에 대한 이 스레드에서 시연될 수 있습니다. 이러한 속도 향상 때문에 최근에는 내장 함수(builtins)에서 키워드 인수를 멀리하는 추세가 있었습니다. 최근에는 bool
, float
, list
, int
, tuple
에 대한 키워드 인수를 허용하지 않도록 하위 호환성이 없는 변경이 이루어졌습니다.
유지보수성 (Maintainability)
Python에서 위치 전용 매개변수를 지정하는 방법을 제공함으로써 C 모듈의 순수 Python 구현을 유지보수하기가 더 쉬워질 것입니다. 또한, 함수를 정의하는 라이브러리 작성자는 키워드 인수를 전달하는 것이 추가적인 명확성을 제공하지 않는다고 판단될 경우 위치 전용 매개변수를 선택할 수 있습니다.
이것은 Python 메일링 리스트에서 잘 논의되고 반복되는 주제입니다.
논리적 순서 (Logical ordering)
위치 전용 매개변수는 이를 사용하는 인터페이스를 호출할 때 어떤 논리적 순서를 강제하는 (사소한) 이점도 있습니다. 예를 들어, range
함수는 모든 매개변수를 위치적으로 받으며 다음과 같은 형태는 허용하지 않습니다.
range(stop=5, start=0, step=2)
range(stop=5, step=2, start=0)
range(step=2, start=0, stop=5)
range(step=2, stop=5, start=0)
이는 (유일하게) 의도된 순서에 대해 키워드 인수를 사용하는 것을 허용하지 않는다는 대가로 이루어집니다.
range(start=0, stop=5, step=2)
순수 Python 및 C 모듈을 위한 호환성 (Compatibility for Pure Python and C Modules)
위치 전용 매개변수의 또 다른 중요한 동기는 PEP 399: 순수 Python/C 액셀러레이터 모듈 호환성 요구사항입니다. 이 PEP는 다음과 같이 명시합니다.
이 PEP는 C 코드가 순수 Python 코드에 사용되는 테스트 스위트를 통과하여 합리적으로 가능한 한 완벽한 대체품 역할을 해야 한다고 요구합니다.
C 코드가 argument clinic
및 관련 도구를 사용하여 위치 전용 매개변수를 구현하기 위한 기존 기능을 사용하여 구현된 경우, 순수 Python 대응물이 제공된 인터페이스 및 요구 사항과 일치하는 것은 불가능합니다. 이는 CPython 표준 라이브러리의 일부 함수 및 클래스와 다른 Python 구현 간에 인터페이스 불일치를 초래합니다. 예를 들어:
$ python3 # CPython 3.7.2
>>> import binascii; binascii.crc32(data=b'data')
TypeError: crc32() takes no keyword arguments
$ pypy3 # PyPy 6.0.0
>>>> import binascii; binascii.crc32(data=b'data')
2918445923
다른 Python 구현은 CPython API를 수동으로 재현할 수 있지만, 이는 Python 표준 라이브러리에 추가되는 모든 모듈이 동일한 인터페이스와 의미론을 가진 순수 Python 구현을 가져야 한다고 의무화하여 노력의 중복을 피하려는 PEP 399의 정신에 어긋납니다.
서브클래스에서의 일관성 (Consistency in Subclasses)
위치 전용 매개변수가 이점을 제공하는 또 다른 시나리오는 서브클래스가 기본 클래스의 메서드를 오버라이드(override)하고 위치적이어야 하는 매개변수의 이름을 변경할 때 발생합니다.
class Base:
def meth(self, arg: int) -> str: ...
class Sub(Base):
def meth(self, other_arg: int) -> str: ...
def func(x: Base):
x.meth(arg=12)
func(Sub()) # 런타임 오류
이 상황은 리스코프 치환 원칙(Liskov Substitution Principle) 위반으로 간주될 수 있습니다. 서브클래스는 기본 클래스의 인스턴스가 예상되는 컨텍스트에서 사용될 수 없습니다. 메서드를 오버로드할 때 인수의 이름을 변경하는 것은 서브클래스가 해당 서브클래스의 특정 도메인에 더 적합한 다른 매개변수 이름을 사용해야 하는 이유가 있을 때 발생할 수 있습니다(예: Mapping
을 서브클래스하여 DNS 조회 캐시를 구현할 때, 파생 클래스는 일반적인 인수 이름 ‘key’와 ‘value’ 대신 ‘host’와 ‘address’를 사용하고 싶을 수 있습니다). 이러한 함수 정의를 위치 전용 매개변수와 함께 사용하면 사용자가 키워드 인수를 사용하여 인터페이스를 호출할 수 없으므로 이 문제를 피할 수 있습니다. 일반적으로 서브클래스를 위한 설계는 아직 작성되지 않았고 작성자가 제어할 수 없는 코드를 예상하는 것을 포함합니다. 하위 호환 가능한 방식으로 인터페이스의 진화를 용이하게 할 수 있는 조치를 갖는 것은 라이브러리 작성자에게 유용할 것입니다.
최적화 (Optimizations)
위치 전용 매개변수를 지지하는 마지막 주장은 매개변수가 엄격한 순서로 전달될 것으로 예상되기 때문에 argument clinic
에 이미 존재하는 것과 같은 일부 새로운 최적화를 허용한다는 것입니다. 예를 들어, CPython의 내부 METH_FASTCALL
호출 규약은 최근에 위치 전용 매개변수를 가진 함수에 대해 빈 키워드를 처리하는 비용을 제거하기 위해 특화되었습니다. 위치 전용 매개변수 덕분에 Python 함수의 평가 프레임을 생성할 때 유사한 성능 개선을 적용할 수 있습니다.
명세 (Specification)
문법 및 의미론 (Syntax and Semantics)
*args
와 **kwargs
를 생략하여 설명하자면, 함수 정의를 위한 문법은 다음과 같습니다.
def name(positional_or_keyword_parameters, *, keyword_only_parameters):
이 예제를 바탕으로, 함수 정의를 위한 새로운 문법은 다음과 같습니다.
def name(positional_only_parameters, /, positional_or_keyword_parameters, *, keyword_only_parameters):
다음과 같은 규칙이 적용됩니다.
/
왼쪽에 있는 모든 매개변수는 위치 전용으로 처리됩니다.- 함수 정의에
/
가 지정되지 않으면, 해당 함수는 위치 전용 인수를 허용하지 않습니다. - 위치 전용 매개변수에 대한 선택적 값(optional values)의 로직은 위치-또는-키워드 매개변수와 동일하게 유지됩니다.
- 위치 전용 매개변수가 기본값(default)과 함께 지정되면, 다음 위치 전용 및 위치-또는-키워드 매개변수도 기본값을 가져야 합니다.
- 기본값이 없는 위치 전용 매개변수는 필수 위치 전용 매개변수입니다.
따라서 다음은 유효한 함수 정의입니다.
def name(p1, p2, /, p_or_kw, *, kw):
def name(p1, p2=None, /, p_or_kw=None, *, kw):
def name(p1, p2=None, /, *, kw):
def name(p1, p2=None, /):
def name(p1, p2, /, p_or_kw):
def name(p1, p2, /):
오늘날과 마찬가지로 다음은 유효한 함수 정의입니다.
def name(p_or_kw, *, kw):
def name(*, kw):
반면에 다음은 유효하지 않습니다.
def name(p1, p2=None, /, p_or_kw, *, kw): # p_or_kw가 기본값 없이 p2=None 뒤에 올 수 없음
def name(p1=None, p2, /, p_or_kw=None, *, kw): # p1=None 뒤에 p2가 기본값 없이 올 수 없음
def name(p1=None, p2, /): # p1=None 뒤에 p2가 기본값 없이 올 수 없음
의미론적 특수 사례 (Semantic Corner Case)
다음은 이 명세의 흥미로운 결과입니다. 이 함수 정의를 고려해 봅시다.
def foo(name, **kwds):
return 'name' in kwds
이것이 True
를 반환하게 만드는 호출은 불가능합니다. 예를 들어:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
하지만 /
를 사용하면 이를 지원할 수 있습니다.
def foo(name, /, **kwds):
return 'name' in kwds
이제 위 호출은 True
를 반환합니다.
즉, 위치 전용 매개변수의 이름은 모호함 없이 **kwds
에서 사용될 수 있습니다. (다른 예로, dict()
와 dict.update()
의 시그니처에 이점이 있습니다.)
구분자로서의 “/”의 유래 (Origin of “/” as a Separator)
/
를 구분자로 사용하는 것은 2012년에 Guido van Rossum에 의해 처음 제안되었습니다.
대안적인 제안: ‘/’는 어떻습니까? 이는 “키워드 인수”를 의미하는 ‘*‘와는 일종의 반대이며, ‘/’는 새로운 문자가 아닙니다.
교육 방법 (How To Teach This)
위치 전용 매개변수를 표시하기 위한 전용 문법을 도입하는 것은 기존의 키워드 전용 인수(keyword-only arguments)와 매우 유사합니다. 이러한 개념들을 함께 가르치는 것이 사용자가 접하거나 설계할 수 있는 가능한 함수 정의를 가르치는 방법을 단순화할 수 있습니다.
이 PEP는 Python 문서의 “함수 정의에 대한 추가 정보(More on Defining Functions)” 섹션에 새로운 하위 섹션을 추가할 것을 권장합니다. 여기서 나머지 인수 유형들이 논의됩니다. 다음 단락들은 이러한 추가 사항의 초안 역할을 합니다. 이들은 위치 전용 및 키워드 전용 매개변수 모두에 대한 표기법을 소개할 것입니다. 이는 포괄적이지도 않으며, 문서에 통합될 최종 버전으로 간주되어서도 안 됩니다.
기본적으로 인수는 Python 함수에 위치적으로 또는 명시적으로 키워드로 전달될 수 있습니다. 가독성과 성능을 위해 인수가 전달되는 방식을 제한하여 개발자가 항목이 위치적으로, 위치적으로 또는 키워드로, 또는 키워드로 전달되는지 여부를 결정하기 위해 함수 정의만 보면 되도록 하는 것이 합리적입니다.
함수 정의는 다음과 같을 수 있습니다.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| 위치-또는-키워드 |
| |
--- 위치 전용 --- 키워드 전용
여기서 /
와 *
는 선택 사항입니다. 사용되는 경우, 이 기호들은 인수가 함수에 전달되는 방식에 따라 매개변수의 종류를 나타냅니다: 위치 전용, 위치-또는-키워드, 그리고 키워드 전용. 키워드 매개변수는 이름 있는 매개변수(named parameters)라고도 불립니다.
위치-또는-키워드 인수 (Positional-or-Keyword Arguments)
함수 정의에 /
와 *
가 없으면, 인수는 함수에 위치적으로 또는 키워드로 전달될 수 있습니다.
위치 전용 매개변수 (Positional-Only Parameters)
조금 더 자세히 살펴보면, 특정 매개변수를 위치 전용으로 표시하는 것이 가능합니다. 위치 전용인 경우, 매개변수의 순서가 중요하며, 매개변수는 키워드로 전달될 수 없습니다. 위치 전용 매개변수는 /
(슬래시) 앞에 위치합니다. /
는 위치 전용 매개변수를 나머지 매개변수로부터 논리적으로 분리하는 데 사용됩니다. 함수 정의에 /
가 없으면, 위치 전용 매개변수는 없습니다.
/
뒤에 오는 매개변수는 위치-또는-키워드이거나 키워드 전용일 수 있습니다.
키워드 전용 인수 (Keyword-Only Arguments)
매개변수를 키워드 전용으로 표시하려면, 즉 매개변수가 키워드 인수로 전달되어야 함을 나타내려면, 첫 번째 키워드 전용 매개변수 바로 앞에 인수 목록에 *
를 배치합니다.
함수 예제 (Function Examples)
/
및 *
마커에 주의하면서 다음 예제 함수 정의를 고려해 봅시다.
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
가장 익숙한 형태인 첫 번째 함수 정의 standard_arg
는 호출 규칙에 제한을 두지 않으며, 인수는 위치적으로 또는 키워드로 전달될 수 있습니다.
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
두 번째 함수 pos_only_arg
는 함수 정의에 /
가 있으므로 위치 매개변수만 사용하도록 제한됩니다.
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
세 번째 함수 kwd_only_args
는 함수 정의에 *
로 표시되어 키워드 인수만 허용합니다.
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
마지막 함수는 동일한 함수 정의에서 세 가지 호출 규칙을 모두 사용합니다.
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
요약 (Recap)
함수 정의에서 어떤 매개변수를 사용할지는 사용 사례에 따라 결정됩니다.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
가이드라인은 다음과 같습니다.
- 이름이 중요하지 않거나 의미가 없고, 항상 동일한 순서로 전달될 소수의 인수만 있을 때 위치 전용(positional-only)을 사용하세요.
- 이름이 의미가 있고 함수 정의가 이름으로 명시될 때 더 이해하기 쉬울 경우 키워드 전용(keyword-only)을 사용하세요.
참조 구현 (Reference Implementation)
CPython 테스트 스위트를 통과하는 초기 구현은 평가를 위해 제공됩니다.
이 구현의 이점은 위치 전용 매개변수 처리 속도, 키워드 전용 매개변수 구현(PEP 3102)과의 일관성, 그리고 이 변경의 영향을 받는 모든 도구 및 모듈의 더 간단한 구현입니다.
거부된 아이디어 (Rejected Ideas)
아무것도 하지 않기 (Do Nothing)
항상 선택 사항입니다. 즉, 현상 유지입니다. 이것이 고려되었지만, 앞서 언급된 이점들은 언어에 추가할 가치가 있습니다.
데코레이터 (Decorators)
이 기능을 위해 Python으로 작성된 데코레이터를 제공하는 것이 python-ideas
에서 제안되었습니다.
이 접근 방식은 함수 정의를 추가 문법으로 오염시키지 않는 이점이 있습니다. 그러나 우리는 다음 이유로 이 아이디어를 거부하기로 결정했습니다.
- 매개변수 동작이 선언되는 방식에 비대칭성을 도입합니다.
- 정적 분석기(static analyzers)와 타입 체커(type checkers)가 위치 전용 매개변수를 안전하게 식별하기 어렵게 만듭니다. 키워드 전용 매개변수는 AST에 직접 노출되는 반면, 이들은 데코레이터 목록을 AST에서 쿼리하고 이름이나 추가 휴리스틱으로 올바른 것을 식별해야 합니다.
- 도구가 위치 전용 매개변수를 올바르게 식별하려면 데코레이터가 설정하는 메타데이터에 접근하기 위해 모듈을 실행해야 합니다. 선언 오류는 런타임에만 보고됩니다.
- 긴 함수 정의에서 위치 전용 매개변수를 식별하기가 더 어려울 수 있습니다. 사용자가 데코레이터의 영향을 받는 마지막 매개변수가 무엇인지 알기 위해 매개변수를 세야 하기 때문입니다.
/
문법은 C 함수에 이미 도입되었습니다. 이 불일치는argument clinic
,inspect
모듈,ast
모듈을 포함하되 이에 국한되지 않는, 이 문법을 다루는 모든 도구 및 모듈을 구현하는 것을 더 어렵게 만들 것입니다.- 데코레이터 구현은 특히 인터프리터에 직접 지원을 추가하는 것과 비교할 때 런타임 성능 비용을 부과할 가능성이 있습니다.
인수별 마커 (Per-Argument Marker)
인수별 마커는 또 다른 언어 고유 옵션입니다. 이 접근 방식은 각 매개변수에 토큰을 추가하여 위치 전용임을 나타내고, 해당 매개변수들이 함께 배치되도록 요구합니다. 예시:
def (.arg1, .arg2, arg3):
.arg1
과 .arg2
에 붙은 점(.)에 주목하십시오. 이 접근 방식은 읽기 쉬울 수 있지만, 명시적인 마커인 /
가 키워드 전용 인수(*
)와 일치하고 오류 발생 가능성이 적기 때문에 거부되었습니다.
일부 라이브러리들은 이미 밑줄 접두사(_
)를 사용하여 매개변수가 위치 전용임을 관례적으로 나타냅니다.
인수별 마커로 “” 사용 (Using “” as a Per-Argument Marker)
일부 라이브러리와 애플리케이션(예: mypy
또는 jinja
)은 이중 밑줄(__
) 접두사를 붙인 이름을 위치 전용 매개변수를 나타내는 관례로 사용합니다. 우리는 __
를 새로운 문법으로 도입하는 아이디어를 다음 이유로 거부했습니다.
- 이는 하위 호환성이 없는 변경입니다.
- 키워드 전용 매개변수가 현재 선언되는 방식과 대칭적이지 않습니다.
- 위치 전용 매개변수를 위해 AST를 쿼리하려면 일반 인수를 확인하고 이름을 검사해야 하는 반면, 키워드 전용 매개변수에는
FunctionDef.args.kwonlyargs
와 같은 속성이 연결되어 있습니다. 위치 전용 인수가 언제 끝나는지 알기 위해 모든 매개변수를 검사해야 합니다. - 마커가 더 장황하여 모든 위치 전용 매개변수를 표시해야 합니다.
- 클래스에서 이름 왜곡(name mangling)을 호출하는 것과 같은 이중 밑줄 접두사의 다른 용도와 충돌합니다.
괄호로 위치 전용 매개변수 그룹화 (Group Positional-Only Parameters With Parentheses)
튜플 매개변수 언패킹(Tuple parameter unpacking)은 함수 정의에서 튜플을 매개변수로 사용할 수 있게 하는 Python 2의 기능입니다. 이는 시퀀스 인수가 자동으로 언패킹되도록 허용합니다. 예시는 다음과 같습니다.
def fxn(a, (b, c), d): pass
튜플 인수 언패킹은 Python 3에서 제거되었습니다(PEP 3113). 이 문법을 재사용하여 위치 전용 매개변수를 구현하자는 제안이 있었습니다. 우리는 여러 가지 이유로 위치 전용 매개변수를 나타내는 이 문법을 거부했습니다.
- 이 문법은 키워드 전용 매개변수가 선언되는 방식에 대해 비대칭적입니다.
- Python 2에서 이 문법을 사용했기 때문에 이 문법의 동작에 대한 혼란을 야기할 수 있습니다. 이는 이 기능을 사용하던 Python 2 코드베이스를 포팅하는 사용자들에게 놀라움을 줄 것입니다.
- 이 문법은 튜플 리터럴과 매우 유사합니다. 이는 튜플 선언과 혼동될 수 있어 추가적인 혼란을 야기할 수 있습니다.
구분자 뒤 제안 (After Separator Proposal)
/
뒤에 위치 매개변수를 표시하는 것은 고려되었던 또 다른 아이디어였습니다. 그러나 마커 뒤의 인수를 수정하는 접근 방식을 찾을 수 없었습니다. 그렇지 않으면 마커 앞의 매개변수도 위치 전용이어야 합니다. 예를 들어:
def (x, y, /, z):
만약 /
가 z
를 위치 전용으로 표시한다고 정의하면, x
와 y
를 키워드 인수로 지정하는 것은 불가능합니다. 현재 키워드 인수가 위치 인수 뒤에 올 수 없다는 점을 고려할 때, 이 제한을 해결할 방법을 찾는 것은 혼란을 가중시킬 것입니다. 따라서 /
는 선행 매개변수와 후행 매개변수 모두를 위치 전용으로 만들 것입니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments