PEP 671 – 지연 바인딩 함수 인자 기본값의 구문
- Author:
- Chris Angelico <rosuav at gmail.com>
- Discussions-To:
- Python-Ideas thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 24-Oct-2021
- Python-Version:
- 3.12
- Post-History:
- 24-Oct-2021, 01-Dec-2021
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
함수 매개변수에는 함수 정의 중 계산되어 저장되는 기본값을 지정할 수 있습니다. 이 제안은 함수 호출 시점에 평가할 표현식으로 정의되는 새로운 형태의 인자 기본값을 도입합니다.
동기
선택적 함수 인자를 생략하면 논리적인 기본값이 사용되는 경우가 많습니다. 이 값이 다른 인자에 의존하거나 각 함수 호출마다 다시 평가되어야 하는 경우, 현재는 함수 헤더에 이를 명확하게 표현할 방법이 없습니다.
현재 사용할 수 있는 관용구에는 다음이 포함됩니다.:
# Very common: Use None and replace it in the function
def bisect_right(a, x, lo=0, hi=None, *, key=None):
if hi is None:
hi = len(a)
# Also well known: Use a unique custom sentinel object
_USE_GLOBAL_DEFAULT = object()
def connect(timeout=_USE_GLOBAL_DEFAULT):
if timeout is _USE_GLOBAL_DEFAULT:
timeout = default_timeout
# Unusual: Accept star-args and then validate
def add_item(item, *optional_target):
if not optional_target:
target = []
else:
target = optional_target[0]
각 형식에서 help(function)는 실제 기본값을 표시하지 못합니다. 또한 각각 추가적인 문제가 있습니다. None을 사용하는 방법은 None 자체가 가능한 함수 매개변수가 아닐 때만 유효하고, 사용자 지정 센티널에는 전역 상수가 필요하며, 별표 인자를 사용하면 둘 이상의 인자를 전달할 수 있음을 의미합니다.
사양
함수 기본 인자는 새로운 =>표기법을 사용하여 정의할 수 있습니다.:
def bisect_right(a, x, lo=0, hi=>len(a), *, key=None):
def connect(timeout=>default_timeout):
def add_item(item, target=>[]):
def format_time(fmt, time_t=>time.time()):
검사를 위해 표현식은 소스 코드 형식으로 저장되며, 이를 평가하는 바이트코드는 함수 본문 앞에 추가됩니다.
특히 이 표현식은 함수가 정의된 스코프가 아니라 함수의 런타임 스코프에서 평가됩니다. 조기 바인딩 기본값은 함수가 정의된 스코프에서 평가됩니다. 따라서 이 표현식에서 다른 인자를 참조할 수 있습니다.
여러 지연 바인딩 인자는 왼쪽에서 오른쪽으로 평가되며, 앞에서 정의된 값을 참조할 수 있습니다. 키워드 인자를 전달하는 순서와 관계없이 순서는 함수에 의해 정의됩니다.
def prevref(word=”foo”, a=>len(word), b=>a//2): # 유효함
평가 순서는 왼쪽에서 오른쪽입니다. 그러나 구현은 먼저 전달된 모든 인자와 조기 바인딩 기본값을 처리하고, 그다음 지연 바인딩 기본값을 처리하는 두 개의 별도 단계로 이를 수행할 수 있습니다. 그 외의 경우에는 모든 인자가 엄격하게 왼쪽에서 오른쪽 순서로 할당됩니다.
채택되지 않은 표기법
이 문서에서는 하나의 구문인 name=>expression을 지정하지만, 다른 표기법도 마찬가지로 그럴듯합니다. 다음 표기법들이 검토되었습니다.:
def bisect(a, hi=>len(a)):
def bisect(a, hi:=len(a)):
def bisect(a, hi?=len(a)):
def bisect(a, @hi=len(a)):
기본 인자는 조기 바인딩인지 지연 바인딩인지에 관계없이 대체로 동일하게 동작하므로, 선택된 구문인 hi=>len(a)은 기존의 조기 바인딩 구문과 의도적으로 유사합니다.
:=구문을 채택하지 않은 이유 중 하나는 어노테이션과 함께 사용될 때의 동작 때문입니다. 어노테이션은 기본값 앞에 오므로, 모든 구문 선택지에서 이것이 어노테이션인지, 기본값인지, 아니면 둘 다인지 사람과 파서 모두가 모호하지 않게 판단할 수 있어야 합니다. 대체 구문인 target:=expr은 어노테이션이 실수로 생략된 target:int=expr로 잘못 해석될 위험이 있으며, 따라서 버그를 숨길 수 있습니다. 선택된 구문인 target=>expr에는 이러한 문제가 없습니다.
이 내용을 가르치는 방법
조기 바인딩 기본 인자는 인자를 평가하는 더 단순하고 효율적인 방법이므로 항상 먼저 가르쳐야 합니다. 이를 바탕으로 하면, 지연 바인딩 인자는 대체로 함수 맨 위에 있는 코드와 동등합니다.:
def add_item(item, target=>[]):
# Equivalent pseudocode:
def add_item(item, target=<OPTIONAL>):
if target was omitted: target = []
간단한 경험칙은 다음과 같습니다. “target=expression”은 함수가 정의될 때 평가되고, “target=>expression”은 함수가 호출될 때 평가됩니다. 어느 경우든 호출 시점에 인자가 제공되면 기본값은 무시됩니다. 이것만으로 모든 세부 사항을 완전히 설명할 수는 없지만, 여기서 중요한 차이점과 둘이 유사하다는 사실을 다루기에는 충분합니다.
다른 제안과의 상호 작용
PEP 661은 이 제안과 동일한 문제 중 하나를 해결하려고 합니다. 이는 기본 인자의 센티널 값에 대한 문서화를 개선하려는 반면, 이 제안은 많은 일반적인 경우에 센티널이 필요하지 않도록 하려 합니다. PEP 661은 임의로 복잡한 함수의 문서화를 개선할 수 있습니다(traceback.print_exception을 주요 동기로 인용하며, 이 함수에는 둘 다 지정되거나 둘 다 지정되지 않아야 하는 두 인자가 있습니다). 반면 함수 자체에서 실제 기본값을 정의할 수 있다면 많은 일반적인 경우에는 더 이상 센티널이 필요하지 않습니다. 또한 전용 센티널 객체를 딕셔너리 조회 키로 사용할 수 있지만, PEP 671은 이에 적용되지 않습니다.
지연 평가를 위한 일반적인 시스템이 때때로 제안되어 왔습니다(어노테이션에만 해당하는 PEP 563 및 PEP 649와 혼동해서는 안 됩니다). 표면적으로는 늦은 바인딩 인자 기본값이 이와 유사한 성격인 것처럼 보일 수 있지만, 실제로는 서로 무관하고 직교하는 개념이며 둘 다 언어에 가치가 있을 수 있습니다. 이 제안의 채택 또는 거부는 지연 평가 제안의 실행 가능성에 영향을 미치지 않으며, 그 반대도 마찬가지입니다. (일반화된 지연 평가와 인자 기본값의 주요 차이점은 인자 기본값은 함수가 실행을 시작할 때 항상, 그리고 오직 그때 평가되는 반면, 지연 표현식은 참조될 때만 실체화된다는 점입니다.)
구현 세부 사항
다음 내용은 참조 구현과 관련된 것이며, 반드시 명세의 일부인 것은 아닙니다.
인자 기본값(위치 인자 또는 키워드 인자)은 이미 보존된 값과 추가 정보 한 항목을 모두 가집니다. 위치 인자의 경우 추가 정보는 __defaults_extra__의 튜플에 저장되고, 키워드 전용 인자의 경우 __kwdefaults_extra__의 딕셔너리에 저장됩니다. 이 속성이 None이면 모든 인자 기본값에 None이 있는 것과 동등합니다.
늦은 바인딩 기본값이 있는 각 매개변수에는 특수 값 Ellipsis가 값 자리 표시자로 저장되며, 해당하는 추가 정보를 조회해야 합니다. None이면 기본값은 실제로 Ellipsis 값입니다. 그렇지 않으면 설명 문자열이며 함수가 실행을 시작할 때 실제 값이 계산됩니다.
늦은 바인딩 기본값이 있는 매개변수가 생략되면 함수는 해당 매개변수가 바인딩되지 않은 상태로 시작합니다. 함수는 새 opcode QUERY_FAST/QUERY_DEREF를 사용하여 늦은 바인딩 기본값이 있는 각 매개변수를 테스트하는 것으로 시작하고, 바인딩되지 않은 경우 원래 표현식을 평가합니다. 이 opcode는 빠른 지역 변수와 클로저 변수에만 사용할 수 있으며, 지정된 지역 변수에 값이 있으면 스택에 True를, 그렇지 않으면 False를 넣습니다. 즉 LOAD_FAST 또는 LOAD_DEREF가 UnboundLocalError를 발생시키면 False를 넣고, 성공하면 True를 넣습니다.
참조 대상이 인자 또는 조기 바인딩 기본값에서 값을 가지는 한, 순서를 벗어난 변수 참조가 허용됩니다.
비용
늦은 바인딩 인자 기본값을 사용하지 않는 경우 다음 비용만 발생해야 합니다.
- 함수 객체에는 추가 포인터 두 개가 필요하며, 이 포인터들은 NULL이 됩니다.
- 코드를 컴파일하고 함수를 생성할 때 추가 플래그 검사가 수행됩니다.
Ellipsis를 기본값으로 사용하면 늦은 바인딩 기본값이 존재하는지 확인하기 위한 런타임 검사가 필요합니다.
이러한 비용은 최소일 것으로 예상됩니다(64비트 Linux에서는 모든 함수 객체의 크기가 152바이트에서 168바이트로 증가합니다). 늦은 바인딩 기본값을 사용하지 않을 때 런타임 비용은 사실상 없습니다.
하위 호환성 문제
늦은 바인딩 기본값을 사용하지 않는 경우 동작은 동일해야 합니다. Ellipsis가 발견되면 주의해야 합니다. Ellipsis가 자기 자신을 나타내지 않을 수 있기 때문입니다. 그 외에는 도구가 기존 코드를 변경되지 않은 것으로 보아야 합니다.
참고 자료
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.