Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 570 – Python 위치 전용 매개변수

Author:
Larry Hastings <larry at hastings.org>, Pablo Galindo Salgado <pablogsal at python.org>, Mario Corchero <mariocj89 at gmail.com>, Eric N. Vander Weele <ericvw at gmail.com>
BDFL-Delegate:
Guido van Rossum <guido at python.org>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Created:
20-Jan-2018
Python-Version:
3.8

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 Python 함수 정의에서 위치 전용 매개변수를 지정하기 위한 새로운 문법인 /를 도입할 것을 제안합니다.

위치 전용 매개변수에는 외부에서 사용할 수 있는 이름이 없습니다. 위치 전용 매개변수를 받는 함수를 호출하면, 위치 인자는 오로지 순서에 따라서만 이 매개변수들에 대응됩니다.

API(애플리케이션 프로그래밍 인터페이스)를 설계할 때, 라이브러리 작성자는 API가 의도한 대로 올바르게 사용되도록 하려고 노력합니다. 어떤 매개변수가 위치 전용인지 지정할 수 없다면, 라이브러리 작성자는 적절한 매개변수 이름을 고를 때 주의를 기울여야 합니다. 이러한 주의는 필수 매개변수에도, 그리고 매개변수가 API 호출자에게 외부적으로 아무런 의미를 갖지 않는 경우에도 기울여야 합니다.

이 PEP에서는 다음을 논의합니다:

  • 위치 전용 매개변수에 대한 Python의 역사와 현재 의미
  • 위치 전용 매개변수가 없어서 겪는 문제들
  • 위치 전용 매개변수에 대한 언어 차원의 지원 없이 이러한 문제들을 처리하는 방법
  • 위치 전용 매개변수가 있을 때의 이점

그런 다음 이러한 동기의 맥락에서 다음을 다룹니다:

  • 위치 전용 매개변수가 언어 고유의 기능이어야 하는 이유 논의
  • 위치 전용 매개변수를 표시하는 문법 제안
  • 이 새로운 기능을 가르치는 방법 제시
  • 기각된 아이디어에 대한 더 자세한 설명

동기

Python에서 위치 전용 매개변수 의미의 역사

Python은 원래 위치 전용 매개변수를 지원했습니다. 초기 버전의 파이썬에는 이름으로 매개변수에 바인딩된 인자를 사용해 함수를 호출하는 기능이 없었습니다. Python 1.0 무렵에 매개변수의 의미가 위치-또는-키워드 방식으로 바뀌었습니다. 그 이후로 사용자는 함수에 인자를 위치로 전달하거나, 함수 정의에 지정된 키워드 이름으로 전달할 수 있게 되었습니다.

현재 버전의 파이썬에서는 CPython의 많은 “내장” 함수와 표준 라이브러리 함수가 위치 전용 매개변수만 받습니다. 그 결과로 나타나는 의미 체계는 이러한 함수 중 하나를 키워드 인자를 사용해 호출해 보면 쉽게 확인할 수 있습니다:

>>> 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()는 필수 매개변수의 왼쪽에 선택적 매개변수를 받습니다. [4]
  • dict()의 경우, 매핑/이터레이터 매개변수는 선택적이며 의미상 반드시 위치 전용이어야 합니다. 이 매개변수에 외부에서 보이는 이름이 있다면, **kwarg 키워드 가변 매개변수 딕셔너리로 들어가는 같은 이름을 가리게 됩니다. [3]

(*args, **kwargs)를 받아 인자를 직접 파싱하면 Python 코드에서도 이러한 의미를 흉내 낼 수 있습니다. 그러나 이렇게 하면 함수 정의와 함수가 계약상 받아들이는 것 사이에 괴리가 생깁니다. 함수 정의가 인자 처리 로직과 일치하지 않습니다.

또한 / 문법은 CPython 외부에서도 비슷한 의미를 지정하는 데 사용되며(예: [1] [2]), 이는 이러한 시나리오가 CPython과 표준 라이브러리에만 국한되지 않음을 보여 줍니다.

위치 전용 매개변수가 없을 때의 문제점

위치 전용 매개변수가 없으면 라이브러리 작성자와 API 사용자 모두 어려움을 겪습니다. 다음 하위 절에서는 각 주체가 겪는 문제를 개괄합니다.

라이브러리 작성자가 겪는 어려움

위치-또는-키워드 매개변수에서는 여러 호출 방식이 섞이는 것이 항상 바람직하지는 않습니다. 작성자는 API를 키워드 인자로 호출하지 못하게 하여 API 사용을 제한하고 싶을 수 있습니다. 키워드 인자로 호출할 수 있으면 매개변수 이름이 공개 API의 일부로 노출되기 때문입니다. 이 방식은 이미 의미를 지닌 필수 함수 매개변수(예: namedtuple(typenames, field_names, …))나 매개변수 이름에 실질적인 외부 의미가 없는 경우(예: min()의 arg1, arg2, … 등)에 특히 유용합니다. API 호출자가 키워드 인자를 사용하기 시작하면 매개변수 이름을 바꾸는 것이 호환성을 깨는 변경이 되므로 라이브러리 작성자는 이름을 바꿀 수 없습니다.

*args에서 인자를 하나씩 꺼내는 방식으로 위치 전용 매개변수를 흉내 낼 수 있습니다. 그러나 앞서 언급했듯이 이 방식은 오류가 발생하기 쉽고 함수 정의와도 일치하지 않습니다. 함수의 사용법이 모호해져서, 사용자는 함수가 계약상 어떤 매개변수를 받아들이는지 이해하기 위해 help(), 관련 자동 생성 문서 또는 소스 코드를 살펴볼 수밖에 없습니다.

API 사용자가 겪는 어려움

사용자는 위치 전용 표기법을 처음 접할 때 놀랄 수 있습니다. 이 표기법은 최근에야 문서화되었고 [13] Python 코드에서는 사용할 수 없으므로 이는 당연한 일입니다. 이러한 이유로 이 표기법은 현재 C로 개발된 CPython API에서만 나타나는 예외적인 존재입니다. 이 표기법을 문서화하고 Python 코드에서도 사용할 수 있게 하면 이러한 괴리가 해소될 것입니다.

게다가 위치 전용 매개변수에 대한 현재 문서는 일관성이 없습니다:

  • 일부 함수는 위치 전용 매개변수의 선택적 그룹을 중첩된 대괄호로 감싸서 나타냅니다. [5]
  • 일부 함수는 매개변수 개수가 서로 다른 여러 프로토타입을 제시하여 위치 전용 매개변수의 선택적 그룹을 나타냅니다. [6]
  • 일부 함수는 위의 두 방식을 모두 사용합니다. [4] [7]

현재 문서가 구분하지 않는 또 다른 점은 함수가 위치 전용 매개변수를 받는지 여부입니다. open()은 키워드 인자를 받지만 ord()는 받지 않습니다. 하지만 기존 문서만 읽어서는 이를 구별할 방법이 없습니다.

위치 전용 매개변수의 이점

위치 전용 매개변수는 라이브러리 작성자에게 더 많은 통제권을 주어 API의 의도된 사용법을 더 잘 표현할 수 있게 하고, API가 하위 호환성을 유지하면서 안전하게 발전할 수 있게 합니다. 또한 Python 언어가 기존 문서 및 여러 “내장” 함수와 표준 라이브러리 함수의 동작과 더 일관되도록 만듭니다.

라이브러리 작성자의 역량 강화

라이브러리 작성자는 호출자를 깨뜨리지 않고도 위치 전용 매개변수의 이름을 유연하게 바꿀 수 있게 됩니다. 이러한 유연성 덕분에 필수 매개변수나 실질적인 외부 의미가 없는 매개변수에 적절한 공개용 이름을 고르는 인지적 부담이 줄어듭니다.

위치 전용 매개변수는 다음과 같은 여러 상황에서 유용합니다:

  • 함수가 임의의 키워드 인자를 받으면서 위치 인자도 받을 수 있는 경우
  • 매개변수에 외부적인 의미가 없는 경우
  • 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.

언어 일관성 향상

위치 전용 매개변수가 있으면 Python 언어는 더 일관성을 갖추게 됩니다. 이 개념이 확장 모듈 전용 기능이 아니라 Python의 일반적인 기능이 되면, 위치 전용 매개변수를 가진 함수를 접하는 사용자의 혼란이 줄어듭니다. 일부 주요 서드파티 패키지는 이미 함수 정의에서 / 표기법을 사용하고 있습니다 [1] [2].

위치 전용 매개변수를 지정하는 “내장” 함수와 위치 인자 문법이 없는 순수 Python 구현 사이의 간극을 메우면 일관성이 향상됩니다. / 문법은 내장 함수와 인터페이스가 인자 Clinic(argument clinic)으로 생성될 때처럼 기존 문서에 이미 드러나 있습니다.

고려해야 할 또 다른 중요한 측면은 PEP 399입니다. 이 PEP는 표준 라이브러리 모듈의 순수 Python 버전이 C로 구현된 가속 모듈과 같은 인터페이스와 의미를 반드시 가져야 한다고 규정합니다. 예를 들어, collections.defaultdict에 순수 Python 구현이 있다면, C 구현의 인터페이스와 일치시키기 위해 위치 전용 매개변수를 사용해야 합니다.

근거

Python 언어에 새로운 문법으로 위치 전용 매개변수를 도입할 것을 제안합니다.

새 문법을 사용하면 라이브러리 작성자는 자신의 API를 호출하는 방식을 더 세밀하게 제어할 수 있습니다. 어떤 매개변수를 위치 전용으로 호출해야 하는지 지정하는 한편, 해당 매개변수를 키워드 인자로 호출하지 못하게 막을 수 있습니다.

이전에 (정보성) PEP 457에서 이 문법을 정의했지만, 그 범위가 훨씬 모호했습니다. 이 PEP는 이 문법의 타당성을 입증하고 함수 정의에서 / 문법의 구현을 제공함으로써 원래 제안을 한 걸음 더 발전시킵니다.

성능

앞서 언급한 이점 외에도, 위치 전용 인자는 파싱과 처리가 더 빠릅니다. 이 성능상의 이점은 키워드 인자를 위치 인자로 변환하는 것에 관한 다음 스레드에서 확인할 수 있습니다: [11]. 이러한 속도 향상 때문에 최근 내장 함수에서 키워드 인자를 없애는 추세가 나타났습니다. 최근에는 bool, float, list, int, tuple에 키워드 인자를 허용하지 않도록 하위 호환성을 깨는 변경이 이루어졌습니다.

유지 보수성

Python에서 위치 전용 매개변수를 지정하는 방법을 제공하면 C 모듈의 순수 Python 구현을 유지 관리하기가 더 쉬워집니다. 또한 함수를 정의하는 라이브러리 작성자는 키워드 인자를 전달해도 명확성이 더해지지 않는다고 판단하면 위치 전용 매개변수를 선택할 수 있습니다.

이는 Python 메일링 리스트에서 충분히 논의되었고 반복해서 등장하는 주제입니다:

논리적 순서

위치 전용 매개변수에는 이를 사용하는 인터페이스를 호출할 때 어느 정도 논리적 순서를 강제한다는 (사소한) 이점도 있습니다. 예를 들어, 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 모듈의 호환성

위치 전용 매개변수가 필요한 또 하나의 중요한 이유는 PEP 399: 순수 Python/C 가속 모듈 호환성 요구 사항입니다. 이 PEP는 다음과 같이 명시합니다:

이 PEP는 이러한 경우에 C 코드가 합리적으로 가능한 한 그대로 대체할 수 있는 수준으로 동작하도록 순수 Python 코드에 사용되는 테스트 스위트를 통과해야 한다고 요구합니다

C 코드가 인자 Clinic(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의 정신에 어긋납니다.

서브클래스에서의 일관성

위치 전용 매개변수가 이점을 주는 또 다른 경우는 서브클래스가 베이스 클래스의 메서드를 오버라이드하면서 위치 인자로 쓰도록 의도된 매개변수의 이름을 바꿀 때입니다:

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())  # Runtime error

이 상황은 리스코프 치환 원칙 위반으로 볼 수 있습니다. 베이스 클래스의 인스턴스가 필요한 곳에 서브클래스를 사용할 수 없기 때문입니다. 메서드를 오버로딩할 때 인자 이름을 바꾸는 일은 서브클래스가 자신의 특정 도메인에 더 적합한 매개변수 이름을 써야 할 이유가 있을 때 생길 수 있습니다(예를 들어 DNS 조회 캐시를 구현하려고 Mapping을 서브클래싱할 때, 파생 클래스는 일반적인 인자 이름인 ‘key’와 ‘value’ 대신 ‘host’와 ‘address’를 쓰고 싶을 수 있습니다). 이 함수 정의에 위치 전용 매개변수를 사용하면 사용자가 키워드 인자로 인터페이스를 호출할 수 없게 되므로 이 문제를 피할 수 있습니다. 일반적으로 서브클래싱을 고려한 설계에는 아직 작성되지 않았고 작성자가 통제할 수도 없는 코드를 예상하는 일이 따르기 마련입니다. 하위 호환성을 유지하면서 인터페이스를 발전시키기 쉽게 해 주는 수단이 있으면 라이브러리 작성자에게 유용할 것입니다.

최적화

위치 전용 매개변수를 지지하는 마지막 논거는, 매개변수가 엄격한 순서대로 전달된다고 예상할 수 있으므로 인자 Clinic(argument clinic)에 이미 있는 것과 같은 몇 가지 새로운 최적화가 가능해진다는 점입니다. 예를 들어 CPython 내부의 METH_FASTCALL 호출 규약은 빈 키워드를 처리하는 비용을 없애기 위해 최근 위치 전용 매개변수를 가진 함수에 맞게 특수화되었습니다. 위치 전용 매개변수 덕분에 Python 함수의 평가 프레임을 만들 때도 비슷한 성능 개선을 적용할 수 있습니다.

명세

문법과 의미

설명을 위해 *args와 **kwargs를 생략하고 “만 피트 상공에서” 내려다보듯 개괄하면, 함수 정의 문법은 다음과 같습니다.:

def name(positional_or_keyword_parameters, *, keyword_only_parameters):

이 예를 바탕으로 하면 새로운 함수 정의 문법은 다음과 같습니다.:

def name(positional_only_parameters, /, positional_or_keyword_parameters,
         *, keyword_only_parameters):

다음 규칙이 적용됩니다.

  • /의 왼쪽에 있는 모든 매개변수는 위치 전용으로 취급됩니다.
  • 함수 정의에 /가 지정되지 않으면 그 함수는 위치 전용 인자를 받지 않습니다.
  • 위치 전용 매개변수의 선택적 값에 관한 규칙은 위치-키워드 겸용 매개변수와 같습니다.
  • 위치 전용 매개변수에 기본값을 지정하면 그 뒤에 오는 위치 전용 매개변수와 위치-키워드 겸용 매개변수에도 기본값이 있어야 합니다.
  • 기본값이 없는 위치 전용 매개변수는 필수 위치 전용 매개변수입니다.

따라서 다음은 올바른 함수 정의입니다.:

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):
def name(p1=None, p2, /, p_or_kw=None, *, kw):
def name(p1=None, p2, /):

전체 문법 명세

제안하는 문법 명세를 간단히 나타내면 다음과 같습니다.:

typedargslist:
  tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [','  # and so on

varargslist:
  vfpdef ['=' test] (',' vfpdef ['=' test])* ',' '/' [','  # and so on

이 PEP의 참조 구현에 따르면 typedarglist에 대한 새 규칙은 다음과 같습니다.:

typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
        '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
      | '**' tfpdef [',']]]
  | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
  | '**' tfpdef [',']] ] )| (
   tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
        '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
      | '**' tfpdef [',']]]
 | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
 | '**' tfpdef [','])

그리고 varargslist에 대한 규칙은 다음과 같습니다.:

varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
        '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
      | '**' vfpdef [',']]]
  | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
  | '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
        '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
      | '**' vfpdef [',']]]
  | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
  | '**' vfpdef [',']
)

의미상의 특수한 경우

다음은 이 명세에서 나오는 흥미로운 결과입니다. 다음 함수 정의를 살펴보십시오.:

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()의 시그니처에도 도움이 됩니다.)

구분자로서 “/”의 기원

/를 구분자로 사용하는 방안은 2012년에 Guido van Rossum이 처음 제안했습니다 [8] :

대안 제안: ‘/’를 사용하는 것은 어떻습니까? 이는 “키워드 인자”를 뜻하는 ‘*’의 반대 같은 것이며, ‘/’는 새로운 문자도 아닙니다.

교육 방법

위치 전용 매개변수를 표시하는 전용 문법을 도입하는 것은 기존의 키워드 전용 인자와 매우 유사합니다. 이 개념들을 함께 가르치면 사용자가 접하거나 설계할 수 있는 함수 정의를 가르치는 방법이 단순해질 수 있습니다.

이 PEP는 나머지 인자 유형을 다루는 Python 문서의 “More on Defining Functions” 섹션에 새로운 하위 섹션을 추가할 것을 권장합니다. 다음 문단들은 이러한 추가 내용의 초안 역할을 합니다. 이 문단들은 위치 전용 매개변수와 키워드 전용 매개변수 모두의 표기법을 소개합니다. 이 내용은 모든 것을 망라하려는 것이 아니며, 문서에 반영될 최종 버전으로 간주해서도 안 됩니다.


기본적으로 인자는 위치로 또는 키워드를 명시하여 Python 함수에 전달할 수 있습니다. 가독성과 성능을 위해, 개발자가 함수 정의만 보고도 항목이 위치로 전달되는지, 위치 또는 키워드로 전달되는지, 키워드로 전달되는지 판단할 수 있도록 인자 전달 방식을 제한하는 것이 합리적입니다.

함수 정의는 다음과 같은 형태일 수 있습니다:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

여기서 /와 *는 선택 사항입니다. 이 기호들을 사용하면 인자가 함수에 전달될 수 있는 방식에 따라 매개변수의 종류, 즉 위치 전용, 위치 또는 키워드, 키워드 전용을 나타냅니다. 키워드 매개변수는 이름 있는 매개변수라고도 합니다.

위치 또는 키워드 인자

함수 정의에 /와 *가 없으면, 인자를 위치 또는 키워드로 함수에 전달할 수 있습니다.

위치 전용 매개변수

좀 더 자세히 살펴보면, 특정 매개변수를 위치 전용으로 표시할 수 있습니다. 위치 전용이면 매개변수의 순서가 중요하며, 해당 매개변수는 키워드로 전달할 수 없습니다. 위치 전용 매개변수는 / (슬래시) 앞에 놓입니다. /는 위치 전용 매개변수를 나머지 매개변수와 논리적으로 구분하는 데 사용됩니다. 함수 정의에 /가 없으면 위치 전용 매개변수도 없습니다.

/ 뒤에 오는 매개변수는 위치 또는 키워드이거나 키워드 전용일 수 있습니다.

키워드 전용 인자

매개변수를 키워드 전용으로 표시하여 해당 매개변수를 반드시 키워드 인자로 전달해야 함을 나타내려면, 인자 목록에서 첫 번째 키워드 전용 매개변수 바로 앞에 *를 두십시오.

함수 예제

마커 /와 *에 주의를 기울이며 다음 함수 정의 예제를 살펴보십시오:

>>> 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'

요약

함수 정의에서 어떤 매개변수를 사용할지는 사용 사례에 따라 결정됩니다:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

지침은 다음과 같습니다:

  • 이름이 중요하지 않거나 아무 의미가 없고, 항상 같은 순서로 전달되는 인자가 몇 개뿐이라면 위치 전용을 사용하십시오.
  • 이름에 의미가 있고 이름을 명시하면 함수 정의를 더 쉽게 이해할 수 있다면 키워드 전용을 사용하십시오.

참조 구현

CPython 테스트 스위트를 통과하는 초기 구현을 평가용으로 사용할 수 있습니다 [10].

이 구현의 이점은 위치 전용 매개변수의 처리 속도, 키워드 전용 매개변수 구현(PEP 3102)과의 일관성, 그리고 이 변경의 영향을 받는 모든 도구와 모듈을 더 간단하게 구현할 수 있다는 점입니다.

기각된 아이디어

아무것도 하지 않기

현상 유지는 언제나 하나의 선택지입니다. 이 방안도 검토했지만, 앞서 언급한 이점들은 언어에 이 기능을 추가할 만한 가치가 있습니다.

데코레이터

python-ideas [9]에서 이 기능을 위해 Python으로 작성한 데코레이터를 제공하자는 제안이 있었습니다.

이 접근 방식은 추가 문법으로 함수 정의를 어지럽히지 않는다는 이점이 있습니다. 그러나 다음과 같은 이유로 이 아이디어를 기각하기로 결정했습니다:

  • 매개변수 동작을 선언하는 방식에 비대칭성이 생깁니다.
  • 정적 분석기와 타입 검사기가 위치 전용 매개변수를 안전하게 식별하기 어려워집니다. 키워드 전용 매개변수는 AST에 직접 드러나는 반면, 이 경우 도구는 AST에서 데코레이터 목록을 조회하고 이름이나 추가적인 휴리스틱으로 올바른 데코레이터를 식별해야 합니다. 도구가 위치 전용 매개변수를 올바르게 식별하려면 데코레이터가 설정하는 메타데이터에 접근하기 위해 모듈을 실행해야 합니다.
  • 선언에 오류가 있어도 런타임에야 보고됩니다.
  • 긴 함수 정의에서는 위치 전용 매개변수를 식별하기가 더 어려울 수 있습니다. 데코레이터의 영향을 받는 마지막 매개변수가 무엇인지 알려면 사용자가 매개변수를 직접 세어야 하기 때문입니다.
  • / 문법은 이미 C 함수에 도입되었습니다. 이러한 불일치 때문에 이 문법을 다루는 도구와 모듈을 구현하기가 더 어려워집니다. 여기에는 인자 Clinic(argument clinic), inspect 모듈, ast 모듈이 포함되며 이에 국한되지 않습니다.
  • 데코레이터 구현은 특히 인터프리터에 직접 지원을 추가하는 방식과 비교하면 런타임 성능 비용을 초래할 가능성이 큽니다.

인자별 표시자

인자별 마커는 언어 자체에 내장할 수 있는 또 다른 방안입니다. 이 방식은 각 매개변수에 토큰을 추가하여 위치 전용임을 나타내며, 해당 매개변수들을 한데 모아 배치하도록 요구합니다. 예시:

def (.arg1, .arg2, arg3):

.arg1과 .arg2에 붙은 점(즉, .)에 주목하십시오. 이 방식이 더 읽기 쉬울 수는 있지만, 명시적 마커로서의 /가 키워드 전용 인자를 위한 *와 일관되고 오류 가능성도 더 낮기 때문에 기각되었습니다.

일부 라이브러리는 이미 선행 밑줄을 [12] 사용하여 매개변수가 위치 전용임을 관례적으로 나타내고 있다는 점에 유의해야 합니다.

“__”를 인자별 마커로 사용하기

일부 라이브러리와 애플리케이션(mypy나 jinja 등)은 위치 전용 매개변수를 나타내는 관례로 이중 밑줄(즉, __)을 앞에 붙인 이름을 사용합니다. 다음과 같은 이유로 __를 새로운 문법으로 도입하는 아이디어를 기각했습니다:

  • 하위 호환성을 깨뜨리는 변경입니다.
  • 현재 키워드 전용 매개변수를 선언하는 방식과 대칭을 이루지 않습니다.
  • AST에서 위치 전용 매개변수를 조회하려면 일반 인자를 확인하고 그 이름을 검사해야 하는 반면, 키워드 전용 매개변수에는 연관된 속성(FunctionDef.args.kwonlyargs)이 있습니다.
  • 위치 전용 인자가 어디서 끝나는지 알려면 모든 매개변수를 검사해야 합니다.
  • 마커가 더 장황하며, 모든 위치 전용 매개변수에 일일이 표시하도록 강제합니다.
  • 클래스에서 이름 맹글링을 일으키는 것과 같은 이중 밑줄 접두사의 다른 용도와 충돌합니다.

위치 전용 매개변수를 괄호로 묶기

튜플 매개변수 언패킹은 함수 정의에서 튜플을 매개변수로 사용할 수 있게 해 주는 Python 2의 기능입니다. 이 기능을 사용하면 시퀀스 인자를 자동으로 언패킹할 수 있습니다. 예를 들면 다음과 같습니다.:

def fxn(a, (b, c), d):
    pass

튜플 인자 언패킹은 Python 3에서 제거되었습니다(PEP 3113). 이 문법을 재사용하여 위치 전용 매개변수를 구현하자는 제안이 있었습니다. 다음과 같은 여러 이유로 위치 전용 매개변수를 나타내는 이 문법을 기각했습니다:

  • 이 문법은 키워드 전용 매개변수를 선언하는 방식과 비대칭적입니다.
  • Python 2에서 이 문법을 사용하므로 이 문법의 동작에 관해 혼란을 일으킬 수 있습니다. 이 기능을 사용하던 Python 2 코드베이스를 이식하는 사용자에게는 뜻밖의 일이 될 것입니다.
  • 이 문법은 튜플 리터럴과 매우 비슷합니다. 튜플 선언과 혼동될 수 있으므로 추가적인 혼란을 일으킬 수 있습니다.

구분자 뒤 표시 제안

/ 뒤에 오는 위치 매개변수를 표시하는 것도 검토된 또 다른 아이디어였습니다. 그러나 마커 뒤의 인자만 변경하는 방법을 찾을 수 없었습니다. 그렇지 않으면 마커 앞의 매개변수까지 위치 전용이 되도록 강제하게 됩니다. 예를 들면 다음과 같습니다.:

def (x, y, /, z):

/가 z를 위치 전용으로 표시한다고 정의하면 x와 y를 키워드 인자로 지정할 수 없게 됩니다. 현재 키워드 인자 뒤에는 위치 인자가 올 수 없으므로, 이 제약을 우회하는 방법을 찾는 것은 혼란을 더할 뿐입니다. 따라서 /는 앞과 뒤에 있는 매개변수를 모두 위치 전용으로 만들게 됩니다.

감사의 말

이 PEP 내용의 일부는 Larry Hastings의 PEP 457에서 가져온 것입니다.

위치 전용 매개변수와 위치 또는 키워드 매개변수 사이의 구분자로 /를 사용하는 방안은 2012년 Guido van Rossum의 제안에서 비롯되었습니다. [8]

문법 단순화에 관한 논의는 Braulio Valdivieso의 공로입니다.