PEP 576 – 내장 함수 클래스 합리화
- Author:
- Mark Shannon <mark at hotpy.org>
- BDFL-Delegate:
- Petr Viktorin
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 10-May-2018
- Python-Version:
- 3.8
- Post-History:
- 17-May-2018, 23-Jun-2018, 08-Jul-2018, 29-Mar-2019
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
CPython 내부에서 사용하는 “FastcallKeywords” 규약을 서드파티 코드에 공개하고, inspect 모듈이 덕 타이핑을 사용하도록 하십시오. 이를 결합하면 서드파티 C 확장과 Cython과 같은 도구가 내장 함수 및 Python 함수와 동일한 호출 규약을 사용하는 객체를 생성할 수 있으므로, len이나 print와 같은 내장 함수와 동등한 성능을 얻을 수 있습니다.
기존 코드의 성능이 소폭 향상될 것으로 예상합니다.
동기
현재 서드파티 모듈 작성자는 C로 함수를 구현할 때 딜레마에 직면합니다. 기존 내장 함수 또는 메서드 클래스 중 하나를 사용하거나, C로 자체 사용자 정의 클래스를 구현해야 합니다. 첫 번째 선택지는 호출 가능 객체의 내부에 접근할 수 있는 능력을 잃게 합니다. 두 번째 선택지는 추가적인 유지 관리 부담을 초래하며, 더 중요하게는 성능에 상당한 부정적인 영향을 미칩니다.
이 PEP는 서드파티 C 모듈 작성자와 Cython과 같은 도구가 CPython 내부에서 내장 함수 및 메서드에 사용되는 더 빠른 호출 규약을 활용하도록 하며, Python으로 구현된 함수에 비해 기능을 잃지 않도록 하는 것을 목표로 합니다.
인트로스펙션
inspect 모듈은 호출 가능 객체를 인트로스펙션할 때 덕 타이핑을 완전히 지원합니다.
inspect.Signature.from_callable() 함수는 호출 가능 객체의 시그니처를 계산합니다. 객체에 __signature__ 속성이 있으면 inspect.Signature.from_callable() 은 단순히 해당 속성을 반환합니다. 덕 타이핑을 더욱 지원하기 위해 호출 가능 객체에 __text_signature__가 있으면 __signature__를 이를 바탕으로 생성합니다.
즉, 서드파티 내장 함수는 적절한 경우 __text_signature__를 구현하고, 필요한 경우 더 비용이 큰 __signature__를 구현할 수 있습니다.
서드파티 호출 가능 객체에 대한 효율적인 호출
현재 대부분의 호출은 “FastcallKeywords” 내부 호출 규약을 사용하여 사용자 정의 코드에서 functions 및 method_descriptors로 디스패치됩니다. 이 PEP는 이 호출 규약을 C 함수 포인터를 통해 구현할 것을 제안합니다. 이 바이너리 인터페이스를 구현하는 서드파티 호출 가능 객체는 내장 함수만큼 빠르게 호출될 가능성이 있습니다.
호출 가능 객체 클래스를 베이스 클래스로 사용하는 행위에 대한 금지 지속
현재 새 클래스의 베이스 클래스로 function, method 또는 method_descriptor를 사용하려는 모든 시도는 TypeError로 실패합니다. 서브클래스가 __call__ 메서드를 재정의할 때 오류가 발생하는 것을 방지하므로 이러한 동작이 바람직합니다. 호출 가능 객체를 서브클래스화할 수 있다면 function 또는 method_descriptor를 호출할 때마다 __call__ 메서드가 재정의되지 않았는지 추가로 확인해야 합니다. 추가 호출 메커니즘을 공개하면 오류가 발생할 가능성이 더 커집니다. 결과적으로 추가 호출 인터페이스를 구현하는 서드파티 클래스는 베이스 클래스로 사용할 수 없습니다.
새 클래스 및 기존 클래스의 변경 사항
Python에 노출되는 변경 사항
- 새로운 내장 클래스인
builtin_function이 추가됩니다. types.BuiltinFunctionType은builtin_function_or_method이 아니라builtin_function을 참조합니다.builtin_function클래스의 인스턴스는builtin_function_or_method의__module__속성을 유지하고,func_module및func_globals속성을 새로 갖게 됩니다.func_module은 함수가 속한 모듈에 액세스할 수 있도록 합니다. 이는 단순히 모듈의 이름을 반환하는__module__속성과는 다르다는 점에 유의하십시오.func_globals속성은func_module.__dict__와 동등하며, 동일한 이름을 가진 Python 함수 속성을 모방하기 위해 제공됩니다.method_descriptor인스턴스를 해당 인스턴스가 속한 클래스의 인스턴스에 바인딩하면builtin_function_or_method대신bound_method가 생성됩니다. 이는 이제method_descriptors가 Python 함수의 동작을 더욱 유사하게 모방한다는 의미입니다. 즉,[].append는builtin_function_or_method대신bound_method가 됩니다.
C API 변경 사항
- 내장 함수를 생성하기 위한 새로운 함수
PyBuiltinFunction_New(PyMethodDef *ml, PyObject *module)가 추가됩니다. PyCFunction_NewEx()및PyCFunction_New()는 사용 중단되며, 가능한 경우PyBuiltinFunction을 반환하고, 그렇지 않으면builtin_function_or_method를 반환합니다.
C API 및 ABI에서 하위 호환성 유지
제안된 변경 사항은 API 및 ABI 수준 모두에서 완전한 하위 호환성과 상위 호환성을 제공합니다.
내부 C 변경 사항
typeobject.tp_flags필드에 두 개의 새로운 플래그가 허용됩니다. 이는 Py_TPFLAGS_EXTENDED_CALL및 Py_TPFLAGS_FUNCTION_DESCRIPTOR입니다.
Py_TPFLAGS_EXTENDED_CALL
Py_TPFLAGS_EXTENDED_CALL을 설정하는 모든 내장 클래스의 경우, 이 내장 클래스에 대응하는 C 구조체는 다음과 같이 정의되는 PyExtendedCallable 구조체로 시작해야 합니다.:
typedef PyObject *(*extended_call_ptr)(PyObject *callable, PyObject** args,
int positional_argcount, PyTupleObject* kwnames);
typedef struct {
PyObject_HEAD
extended_call_ptr ext_call;
} PyExtendedCallable;
Py_TPFLAGS_EXTENDED_CALL을 설정하는 클래스는 베이스 클래스로 사용될 수 없으며, 어떤 파이썬 코드가 이를 베이스 클래스로 사용하려 하면 TypeError가 발생합니다.
Py_TPFLAGS_FUNCTION_DESCRIPTOR
이 플래그가 내장 클래스 F에 설정되어 있으면, 해당 클래스의 인스턴스는 클래스 속성으로 사용될 때 파이썬 함수와 동일하게 동작할 것으로 기대됩니다. 구체적으로, 이는 C.m이 내장 클래스 F의 인스턴스이고 c가 C의 인스턴스일 때, c.m의 값이 C.m과 c를 바인딩하는 바운드 메서드여야 함을 의미합니다. 이 플래그가 없다면, 커스텀 호출 가능 객체가 파이썬 함수처럼 동작하면서 동시에 파이썬 함수나 내장 함수만큼 효율적일 수는 없을 것입니다.
기존 C 구조체에 대한 변경
function, method_descriptor, method 클래스는 이에 대응하는 구조체가 PyExtendedCallable 구조체로 시작하도록 변경됩니다.
새로운 확장 호출 인터페이스를 사용하는 서드파티 내장 클래스
파이썬 함수 및 내장 함수와 대등한 호출 성능을 내려면, 서드파티 호출 가능 객체는 tp_flags의 Py_TPFLAGS_EXTENDED_CALL 비트를 설정하고, 이에 대응하는 C 구조체가 PyExtendedCallable로 시작하도록 해야 합니다. Py_TPFLAGS_EXTENDED_CALL 비트가 설정된 모든 내장 클래스는 tp_call 함수도 구현해야 하며, 그 동작이 ext_call 함수와 일관되도록 해야 합니다.
이러한 변경이 성능에 미치는 영향
함수 포인터를 각 호출 가능 객체의 클래스가 아니라 각 호출 가능 객체 자체에 추가하면, 디스패치 함수(인자를 정리하고 오류 검사를 수행하는 코드)의 선택을 호출 시점이 아니라 호출 가능 객체가 생성되는 시점에 할 수 있게 됩니다. 이는 인터프리터의 호출 지점과 피호출자의 실행 사이에서 실행되는 명령어 수를 줄여줄 것입니다.
대안 제안
PEP 580은 이 PEP와 동일한 문제를 해결하기 위한 대안적 접근 방식입니다.
참조 구현
초안 구현은 https://github.com/markshannon/cpython/tree/pep-576-minimal 에서 찾을 수 있습니다
Copyright
This document has been placed in the public domain.