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 무렵에 매개변수 의미가 위치 또는 키워드 방식으로 변경되었습니다. 그 이후 사용자는 함수 정의에 지정된 키워드 이름이나 위치를 사용하여 함수에 인자를 제공할 수 있게 되었습니다.

현재 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()는 오버로드된 함수로서 필수 매개변수의 왼쪽에 선택적 매개변수를 허용합니다. [4]
  • dict()는 매핑/이터레이터 매개변수가 선택 사항이며 의미상 반드시 위치 전용이어야 합니다. 이 매개변수에 외부에서 볼 수 있는 이름을 지정하면 해당 이름이 **kwarg 키워드 가변 매개변수 딕셔너리로 전달되는 것을 가리게 됩니다. [3]

(*args, **kwargs)를 받아들이고 인자를 수동으로 분석하면 Python 코드에서 이러한 의미 체계를 모방할 수 있습니다. 그러나 이 경우 함수 정의와 함수가 계약상 받아들이는 내용 사이에 괴리가 발생합니다. 함수 정의가 인자 처리 로직과 일치하지 않습니다.

또한 / 구문은 유사한 의미 체계를 지정하기 위해 CPython 외부에서도 사용됩니다(즉, [1] [2]). 따라서 이러한 시나리오는 CPython과 표준 라이브러리에만 국한되지 않음을 알 수 있습니다.

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

위치 전용 매개변수가 없으면 라이브러리 작성자와 API 사용자에게 여러 가지 어려움이 발생합니다. 다음 하위 절에서는 각 주체가 겪는 문제를 설명합니다.

라이브러리 작성자의 과제

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

*args에서 인자를 하나씩 추출하면 위치 전용 매개변수를 모방할 수 있습니다. 그러나 앞서 언급했듯이 이 접근 방식은 오류가 발생하기 쉽고 함수 정의와 동의어가 아닙니다. 함수 사용 방식이 모호해지며, 사용자는 함수가 계약상 어떤 매개변수를 받아들이는지 이해하기 위해 help()나 관련 자동 생성 문서 또는 소스 코드를 확인해야 합니다.

API 사용자의 과제

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

또한 위치 전용 매개변수에 대한 현재 문서는 일관되지 않습니다.

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

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

위치 전용 매개변수의 이점

위치 전용 매개변수는 라이브러리 작성자가 API의 의도된 사용 방식을 더 잘 표현하도록 더 많은 통제권을 제공하며, API를 안전하고 하위 호환 가능한 방식으로 발전시킬 수 있게 합니다. 또한 Python 언어를 기존 문서 및 다양한 “내장” 함수와 표준 라이브러리 함수의 동작에 더욱 일관되게 만듭니다.

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

라이브러리 작성자는 호출자를 중단시키지 않고 위치 전용 매개변수의 이름을 변경할 수 있는 유연성을 갖게 됩니다. 이러한 유연성은 필수 매개변수나 외부적으로 실제 의미를 지니지 않는 매개변수에 적절한 공개 이름을 선택할 때의 인지적 부담을 줄입니다.

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

  • 함수가 어떤 키워드 인자든 받아들이면서 위치 인자도 받아들일 수 있을 때
  • 매개변수가 외부적으로 의미를 갖지 않을 때
  • API의 매개변수가 필수이고 모호하지 않을 때

주요 시나리오는 함수가 어떤 키워드 인자든 받아들이면서 위치 인자도 받아들일 수 있는 경우입니다. 대표적인 예로는 Formatter.formatdict.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].

위치 전용 매개변수를 지정하는 “builtin” 함수와 위치 문법이 없는 순수 Python 구현 사이에 존재하는 차이를 해소하면 일관성이 향상됩니다. / 구문은 argument clinic이 내장 함수와 인터페이스를 생성하는 경우 등 기존 문서에 이미 공개되어 있습니다.

고려해야 할 또 다른 필수적인 측면은 PEP 399입니다. 이 규정은 표준 라이브러리 모듈의 순수 Python 버전이 C로 구현된 가속기 모듈과 동일한 인터페이스와 의미를 must 가져야 한다고 규정합니다. 예를 들어 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 코드가 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’를 사용하고 싶을 수 있습니다). 위치 전용 매개변수를 사용하는 이 함수 정의를 적용하면 사용자가 키워드 인자를 사용하여 인터페이스를 호출할 수 없으므로 이 문제를 피할 수 있습니다. 일반적으로 서브클래싱을 고려하여 설계하는 일은 아직 작성되지 않았으며 작성자가 제어할 수 없는 코드를 예측하는 작업을 수반합니다. 인터페이스의 하위 호환 가능한 발전을 촉진할 수 있는 조치를 마련하면 라이브러리 작성자에게 유용할 것입니다.

최적화

위치 전용 매개변수를 지지하는 마지막 논거는 매개변수가 엄격한 순서로 전달될 것으로 예상되기 때문에 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):

다음 사항이 적용됩니다.

  • /의 왼쪽에 있는 모든 매개변수는 위치 전용으로 처리됩니다.
  • 함수 정의에 /가 지정되지 않으면 해당 함수는 위치 전용 인자를 허용하지 않습니다.
  • 위치 전용 매개변수의 선택적 값에 관한 규칙은 위치 또는 키워드 매개변수의 경우와 동일하게 유지됩니다.
  • 위치 전용 매개변수에 기본값이 지정되면 그 뒤에 오는 위치 전용 매개변수와 위치 또는 키워드 매개변수에도 기본값이 지정되어야 합니다.
  • 기본값이 없는 위치 전용 매개변수는 required 위치 전용 매개변수입니다.

따라서 다음과 같은 함수 정의가 유효합니다.:

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] 에서 이 기능을 위해 파이썬으로 작성된 데코레이터를 제공하자는 제안이 있었습니다.

이 접근 방식은 함수 정의에 추가 구문을 오염시키지 않는다는 이점이 있습니다. 그러나 우리는 다음과 같은 이유로 이 아이디어를 거부하기로 결정했습니다:

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

인자별 마커

인자별 마커는 언어 자체에 내재된 또 다른 선택지입니다. 이 접근 방식은 각 매개변수에 토큰을 추가하여 위치 전용임을 나타내며, 이러한 매개변수들이 함께 배치되도록 요구합니다. 예제:

def (.arg1, .arg2, arg3):

.arg1.arg2에 붙은 점(즉, .)에 주목하십시오. 이 접근 방식이 읽기에는 더 쉬울 수 있지만, 명시적 마커로서의 /가 키워드 전용 인자에 대한 *와 일관성이 있고 오류가 발생하기 어렵다는 이유로 거부되었습니다.

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

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

일부 라이브러리와 애플리케이션(예: mypyjinja)은 위치 전용 매개변수를 나타내는 관례로 이중 밑줄(즉, __)을 이름 앞에 붙입니다. 우리는 다음과 같은 이유로 __를 새로운 문법으로 도입하는 것을 거부했습니다.

  • 이는 하위 호환성을 깨뜨리는 변경입니다.
  • 이는 현재 키워드 전용 매개변수가 선언되는 방식과 대칭적이지 않습니다.
  • 위치 전용 매개변수를 확인하기 위해 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를 위치 전용으로 표시한다고 정의하면, xy를 키워드 인자로 지정하는 것이 불가능해집니다. 이 제한을 우회할 방법을 찾는 것은, 현재 키워드 인자 뒤에 위치 인자가 올 수 없다는 점을 고려할 때 혼란을 더할 것입니다. 따라서 /는 앞뒤에 있는 매개변수를 모두 위치 전용으로 만들 것입니다.

감사의 말

이 PEP의 일부 내용에 대한 공은 Larry Hastings의 PEP 457에 있습니다.

위치 전용 매개변수와 위치 또는 키워드 매개변수 사이의 구분자로 /를 사용한 것에 대한 공은 Guido van Rossum에게 있으며, 이는 다음의 제안에서 비롯되었습니다 2012. [8]

문법 단순화에 대한 논의에 대한 공은 Braulio Valdivieso에게 있습니다.