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” 내부 호출 규약을 사용하여 function과 method_descriptor로 디스패치됩니다. 이 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을 설정한 클래스는 베이스 클래스로 사용할 수 없으며, Python 코드가 이를 베이스 클래스로 사용하려고 하면 TypeError가 발생합니다.
Py_TPFLAGS_FUNCTION_DESCRIPTOR
내장 클래스 F에 이 플래그가 설정되어 있으면, 해당 클래스의 인스턴스는 클래스 속성으로 사용될 때 Python 함수와 똑같이 동작해야 합니다. 구체적으로, 이는 C.m이 내장 클래스 F의 인스턴스일 때(그리고 c가 C의 인스턴스일 때) c.m의 값이 C.m과 c를 바인딩하는 바운드 메서드여야 한다는 뜻입니다. 이 플래그가 없으면 사용자 정의 호출 가능 객체가 Python 함수처럼 동작하면서 동시에 Python 함수나 내장 함수만큼 효율적일 수는 없습니다.
기존 C 구조체의 변경 사항
function, method_descriptor, method 클래스는 각각 대응하는 구조체가 PyExtendedCallable 구조체로 시작하도록 변경됩니다.
새로운 확장 호출 인터페이스를 사용하는 서드파티 내장 클래스
Python 함수 및 내장 함수와 대등한 호출 성능을 얻으려면, 서드파티 호출 가능 객체는 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.