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

Python 개선 제안 한국어 번역

PEP 575 – 함수/메서드 클래스 통합

Author:
Jeroen Demeyer <J.Demeyer at UGent.be>
Status:
Withdrawn
Type:
Standards Track
Created:
27-Mar-2018
Python-Version:
3.8
Post-History:
31-Mar-2018, 12-Apr-2018, 27-Apr-2018, 05-May-2018

Table of Contents

번역·라이선스 안내

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

철회 공지

사용자 정의 클래스의 빠른 호출을 가능하게 하는 더 나은 해결책은 PEP 580을 참조하십시오.

이 PEP의 다른 일부 문제에 대한 더 폭넓은 논의는 PEP 579를 참조하십시오.

초록

내장 함수(C로 구현됨)와 Python 함수 간의 차이를 줄이는 것을 목표로 함수와 메서드의 클래스 계층 구조를 재구성합니다. 주로 성능을 희생하지 않으면서 내장 함수가 Python 함수와 더 유사하게 동작하도록 합니다.

새로운 베이스 클래스 base_function이 도입되며, 다양한 함수 클래스와 method(bound_method로 이름이 변경됨)가 이 클래스를 상속합니다.

또한 Python function 클래스의 서브클래싱을 허용합니다.

동기

현재 CPython에는 두 가지 서로 다른 함수 클래스가 있습니다. 첫 번째는 def 또는 lambda로 함수를 정의할 때 얻는 Python 함수입니다. 두 번째는 len, isinstance 또는 numpy.dot과 같은 내장 함수입니다. 이러한 함수는 C로 구현됩니다.

이 두 클래스는 완전히 독립적으로 구현되며 기능도 서로 다릅니다. 특히 현재는 C에서 함수를 효율적으로 구현하면서(이를 수행할 수 있는 것은 내장 함수뿐입니다) inspect.signature 또는 inspect.getsourcefile과 같은 인트로스펙션을 허용하는 것이 불가능합니다(이를 수행할 수 있는 것은 Python 함수뿐입니다). 이는 바로 그러한 작업을 수행하려는 Cython [1]과 같은 프로젝트에 문제가 됩니다.

Cython에서는 cyfunction이라는 새로운 함수 클래스를 만들어 이 문제를 우회했습니다. 안타깝게도 새로운 함수 클래스는 문제를 일으킵니다. inspect 모듈은 이러한 함수를 함수로 인식하지 않으며 [2], 성능도 더 낮습니다(CPython에는 내장 함수 호출을 위한 특정 최적화가 있습니다).

두 번째 동기는 보다 일반적으로 내장 함수와 메서드가 Python 함수 및 메서드와 더 유사하게 동작하도록 만드는 것입니다. 예를 들어 Python의 언바운드 메서드는 단순한 함수이지만, 확장 타입의 언바운드 메서드(예: dict.get)는 별개의 클래스입니다. Python 클래스의 바운드 메서드에는 __func__ 어트리뷰트가 있지만, 확장 타입의 바운드 메서드에는 없습니다.

셋째, 이 PEP는 함수의 폭넓은 사용자 지정을 허용합니다. function 클래스를 서브클래싱할 수 있게 되며, C로 구현된 함수에도 사용자 정의 함수 서브클래스를 사용할 수 있습니다. 후자의 경우 실제 내장 함수와 동일한 성능으로 이를 수행할 수 있습니다. 모든 함수가 함수 객체(__call__self)에 액세스할 수 있으므로 PEP 573을 위한 길이 열립니다.

새로운 클래스

다음은 함수와 메서드를 위한 새로운 클래스 계층 구조입니다.:

              object
                 |
                 |
          base_function
         /       |     \
        /        |      \
       /         |   defined_function
      /          |        \
cfunction (*)    |         \
                 |       function
                 |
           bound_method (*)

(*)로 표시된 두 클래스는 서브클래싱을 허용하지 않으며, 나머지 클래스는 허용합니다.

함수와 언바운드 메서드 간에는 차이가 없으며, 바운드 메서드는 bound_method의 인스턴스입니다.

base_function

base_function 클래스는 모든 함수 타입을 위한 새로운 베이스 클래스가 됩니다. 이 클래스는 기존 builtin_function_or_method 클래스를 기반으로 하지만, 다음과 같은 차이점과 새로운 기능이 있습니다.

  1. m_selfNULL인 경우 함수가 메서드로 변환되도록 __get__를 구현하는 디스크립터 역할을 합니다. m_selfNULL이 아니면 아무 작업도 수행하지 않고 기존 함수를 대신 반환합니다.
  2. C 구조에서는 m_parent로 표현되는 새로운 읽기 전용 속성 __parent__가 추가됩니다. 이 속성이 존재하면 정의 객체를 나타냅니다. 확장 타입의 메서드에서는 정의 클래스(일반 Python에서 __class__)를 나타내며, 모듈의 함수에서는 정의 모듈을 나타냅니다. 일반적으로 이는 어떤 Python 객체든 될 수 있습니다. __parent__가 클래스이면 특별한 의미를 가집니다. 이 경우 함수는 해당 클래스의 인스턴스인 self와 함께 호출되어야 합니다. 마지막으로 __qualname____reduce__는 네임스페이스로 __self__ 대신 __parent__를 사용합니다.
  3. __parent__가 클래스인 경우 __parent__와 같은 값을 가지는 새로운 속성 __objclass__가 추가됩니다. 그렇지 않으면 __objclass__에 접근할 때 AttributeError가 발생합니다. 이는 method_descriptor와의 하위 호환성을 위한 것입니다.
  4. ml_doc 필드와 __doc____text_signature__ 속성(Argument Clinic 참조)은 지원되지 않습니다.
  5. ml_flags를 위한 새로운 플래그 METH_PASS_FUNCTION이 추가됩니다. 이 플래그가 설정되면 ml_meth에 저장된 C 함수가 함수 객체와 같은 추가 첫 번째 인자를 받아 호출됩니다.
  6. ml_flags를 위한 새로운 플래그 METH_BINDING이 추가되며, 이는 모듈의 함수에만 적용되고 클래스의 메서드에는 적용되지 않습니다. 이 플래그가 설정되면 m_self는 모듈 대신 NULL로 설정됩니다. 이를 통해 __get__을 활성화하여 함수가 Python 함수와 더 유사하게 동작할 수 있습니다.
  7. Self slicing을 비활성화하는 새로운 플래그 METH_CALL_UNBOUND가 추가됩니다.
  8. ml_flags를 위한 새로운 플래그 METH_PYTHON이 추가됩니다. 이 플래그는 이 함수를 Python 함수로 취급해야 함을 나타냅니다. 이상적으로는 덕 타이핑 철학에 어긋나므로 이 플래그의 사용을 피해야 합니다. 그러나 Profiling의 경우처럼 몇몇 곳에서는 여전히 필요합니다.

base_function의 목표는 함수와 메서드를 호출하는 서로 다른 모든 방식을 하나의 구조체에서 지원하는 것입니다. 예를 들어 새로운 플래그 METH_PASS_FUNCTION은 메서드 구현에서 사용됩니다.

base_function의 인스턴스는 직접 생성할 수 없습니다(tp_newNULL입니다). 그러나 C 코드가 인스턴스를 수동으로 생성하는 것은 허용됩니다.

다음은 관련 C 구조체입니다.:

PyTypeObject PyBaseFunction_Type;

typedef struct {
    PyObject_HEAD
    PyCFunctionDef *m_ml;     /* Description of the C function to call */
    PyObject *m_self;         /* __self__: anything, can be NULL; readonly */
    PyObject *m_module;       /* __module__: anything (typically str) */
    PyObject *m_parent;       /* __parent__: anything, can be NULL; readonly */
    PyObject *m_weakreflist;  /* List of weak references */
} PyBaseFunctionObject;

typedef struct {
    const char *ml_name;   /* The name of the built-in function/method */
    PyCFunction ml_meth;   /* The C function that implements it */
    int ml_flags;          /* Combination of METH_xxx flags, which mostly
                              describe the args expected by the C func */
} PyCFunctionDef;

서브클래스는 추가 필드로 PyCFunctionDef를 확장할 수 있습니다.

METH_STATIC이 설정된 경우를 제외하면 Python 속성 __self__m_self를 반환합니다. 이 경우 또는 m_selfNULL인 경우에는 __self__ 속성이 전혀 존재하지 않습니다. 이러한 이유로 이 PEP에서는 약간 다른 의미로 m_self 또는 __self__를 씁니다.

cfunction

이는 기존 builtin_function_or_method 클래스의 새로운 버전입니다. cfunction이라는 이름은 builtins 모듈에 있는 무언가라는 의미의 “내장”과 혼동하지 않도록 선택되었습니다. 또한 PyCFunction 접두사를 사용하는 C API에 더 잘 부합합니다.

cfunction 클래스는 base_function의 복사본이며, 다음과 같은 차이점이 있습니다.

  1. m_mlPyCFunctionDef를 확장하고 __doc____text_signature__를 읽기 전용 특성으로 구현하기 위한 추가 ml_doc 필드가 있는 PyMethodDef 구조체를 가리킵니다.:
    typedef struct {
        const char *ml_name;
        PyCFunction ml_meth;
        int ml_flags;
        const char *ml_doc;
    } PyMethodDef;
    

    PyMethodDefPython Stable ABI의 일부이며 거의 모든 확장 모듈에서 사용되므로, 이 구조체는 절대로 변경할 수 없다는 점에 유의하십시오.

  2. Argument Clinic이 지원됩니다.
  3. __self__은 항상 존재합니다. base_function.__self__AttributeError를 발생시킬 경우에는 대신 None이 반환됩니다.

타입 객체는 PyTypeObject PyCFunction_Type이며, PyCFunctionObjectPyBaseFunctionObject의 별칭으로 정의합니다(m_ml의 타입은 제외합니다).

defined_function

defined_function 클래스는 함수가 인트로스펙션을 지원한다는 것을 나타내기 위한 추상 베이스 클래스입니다. defined_function의 인스턴스는 Python 함수가 가지는 모든 특성, 즉 __code__, __globals__, __doc__, __defaults__, __kwdefaults__, __closure____annotations__를 지원해야 합니다. 사용자에 의해 추가된 특성을 지원하기 위한 __dict__도 있습니다.

이들 중 어느 것도 의미 있는 값을 가져야 하는 것은 아닙니다. 특히 __code__는 동작하는 코드 객체가 아닐 수 있으며, 몇 개의 필드만 채워져 있을 수도 있습니다. 이 PEP에서는 다양한 특성이 어떻게 구현되는지 규정하지 않습니다. 이들은 단순한 구조체 멤버일 수도 있고 더 복잡한 디스크립터일 수도 있습니다. 읽기 전용 지원만 필요하며, 어떤 특성도 쓰기 가능해야 할 필요는 없습니다.

defined_function 클래스는 주로 Cython [1]에서 생성되는 것과 같은 자동 생성 C 코드를 위한 것입니다. 이 클래스의 인스턴스를 생성하는 API는 없습니다.

C 구조체는 다음과 같습니다.:

PyTypeObject PyDefinedFunction_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *func_dict;        /* __dict__: dict or NULL */
} PyDefinedFunctionObject;

TODO: defined_function에 더 나은 이름을 붙일 수 있는지 검토하십시오. 다른 제안으로는 inspect_function(inspect.isfunction을 만족하는 모든 것), builtout_function(더 잘 구축된 함수라는 의미이며 builtin을 이용한 말장난), generic_function(원래 제안된 이름이지만 functools.singledispatch의 제너릭 함수와 충돌함), user_function(CPython과 대조하여 사용자가 정의한 함수)가 있습니다.

function

이는 Python으로 구현된 함수를 위한 클래스입니다. 다른 함수 타입과 달리 function의 인스턴스는 Python 코드에서 생성할 수 있습니다. 이 부분은 변경되지 않았으므로 이 PEP에서는 세부 사항을 설명하지 않습니다.

C 구조체의 레이아웃은 다음과 같습니다.:

PyTypeObject PyFunction_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *func_dict;        /* __dict__: dict or NULL */
    PyObject *func_code;        /* __code__: code */
    PyObject *func_globals;     /* __globals__: dict; readonly */
    PyObject *func_name;        /* __name__: string */
    PyObject *func_qualname;    /* __qualname__: string */
    PyObject *func_doc;         /* __doc__: can be anything or NULL */
    PyObject *func_defaults;    /* __defaults__: tuple or NULL */
    PyObject *func_kwdefaults;  /* __kwdefaults__: dict or NULL */
    PyObject *func_closure;     /* __closure__: tuple of cell objects or NULL; readonly */
    PyObject *func_annotations; /* __annotations__: dict or NULL */
    PyCFunctionDef _ml;         /* Storage for base.m_ml */
} PyFunctionObject;

디스크립터 __name__func_name을 반환합니다. __name__을 설정하면 base.m_ml->ml_name도 UTF-8로 인코딩된 이름으로 업데이트됩니다.

_ml 필드는 base.m_ml에서 사용할 공간을 예약합니다.

base_function인스턴스는 function의 인스턴스인 경우에 한해서만 METH_PYTHON플래그가 설정되어 있어야 합니다.

codeglobalsfunction의 인스턴스를 생성할 때, base.m_ml = &_ml, base.m_self = NULL인 인스턴스를 생성합니다.

서브클래싱을 쉽게 하기 위해 복사 생성자도 추가합니다. ffunction의 인스턴스라면 types.FunctionType(f)f를 복사합니다. 이를 통해 사용자 정의 함수 타입을 데코레이터로 편리하게 사용할 수 있습니다.:

>>> from types import FunctionType
>>> class CustomFunction(FunctionType):
...     pass
>>> @CustomFunction
... def f(x):
...     return x
>>> type(f)
<class '__main__.CustomFunction'>

또한 functools.wraps를 사용하는 많은 경우를 없앨 수 있습니다. 래퍼를 function의 서브클래스로 대체할 수 있습니다.

bound_method

bound_method클래스는 기반 함수의 클래스와 관계없이 모든 바인딩된 메서드에 사용됩니다. base_function위에 새로운 속성 하나를 추가합니다. __func__는 해당 함수를 가리킵니다.

bound_method는 메서드로 바인딩된 Python 함수에만 사용되던 기존 method클래스를 대체합니다.

임의의 호출 가능 객체로부터 메서드를 생성할 수 있도록 하려 하기 때문에 한 가지 문제가 있습니다. 이는 이미 바인딩된 메서드일 수도 있고, 단순히 base_function의 인스턴스가 아닐 수도 있습니다. 따라서 실제로는 두 종류의 메서드가 있습니다.

  • 임의의 호출 가능 객체에는 METH_PASS_FUNCTION플래그가 설정된 하나의 고정된 PyCFunctionDef구조체를 사용합니다.
  • Self slicing을 가지며 base_function의 인스턴스를 바인딩하는 메서드(더 정확히는 Py_TPFLAGS_BASEFUNCTION플래그가 설정된 메서드)에는 대신 원래 함수의 PyCFunctionDef를 사용합니다. 이렇게 하면 바인딩된 메서드를 호출할 때 성능을 잃지 않습니다. 이 경우 __func__속성은 다양한 속성을 구현하는 데만 사용되며, 메서드를 호출하는 데는 사용되지 않습니다.

base_function으로부터 새 메서드를 생성할 때 self객체가 __objclass__의 인스턴스인지(클래스가 부모로 지정된 경우) 확인하고, 그렇지 않으면 TypeError를 발생시킵니다.

C 구조체는 다음과 같습니다.:

PyTypeObject PyMethod_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *im_func;  /* __func__: function implementing the method; readonly */
} PyMethodObject;

base_function 인스턴스 호출

base_function의 인스턴스에 대한 __call__의 구현을 지정합니다.

__objclass__ 확인

우선 함수의 __parent__가 클래스인 경우 타입 검사를 수행합니다(__objclass__가 이후 __parent__의 별칭이 된다는 점을 기억하십시오). m_selfNULL이면(확장 타입의 바인딩되지 않은 메서드에서 이러한 경우가 발생합니다), 함수를 하나 이상의 위치 인자와 함께 호출해야 하며 첫 번째 인자(일반적으로 self라고 부릅니다)는 __objclass__의 인스턴스여야 합니다. 그렇지 않으면 TypeError를 발생시킵니다.

바인딩된 메서드는 m_self != NULL이므로 __objclass__를 확인하지 않는다는 점에 유의하십시오. 대신 메서드를 생성할 때 __objclass__를 확인합니다.

플래그

편의를 위해 새 상수를 정의합니다. METH_CALLFLAGS는 호출할 C 함수의 시그니처를 지정하는 PyCFunctionDef.ml_flags의 모든 플래그를 결합합니다. 이는 다음과 같습니다.

METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS | METH_PASS_FUNCTION

위의 처음 네 플래그 중 정확히 하나가 설정되어야 하며, METH_VARARGSMETH_FASTCALLMETH_KEYWORDS와 결합할 수 있습니다. 이러한 규칙을 위반하면 동작이 정의되지 않습니다.

함수 호출에 영향을 미치는 새 플래그가 두 개 있으며, 즉 METH_PASS_FUNCTIONMETH_CALL_UNBOUND입니다. 일부 플래그는 이미 [5]에 문서화되어 있습니다. 나머지 플래그는 아래에서 설명합니다.

셀프 슬라이싱

함수에 m_self == NULL이고 플래그 METH_CALL_UNBOUND이 설정되지 않은 경우, 첫 번째 위치 인자(있는 경우)가 *args에서 제거되고 대신 C 함수의 첫 번째 인자로 전달됩니다. 사실상 첫 번째 위치 인자는 __self__로 취급됩니다. 이는 언바운드 메서드를 지원하기 위한 것으로, C 함수가 바운드 메서드 호출과 언바운드 메서드 호출의 차이를 인식하지 않도록 합니다. 이는 키워드 인자에는 어떤 방식으로도 영향을 미치지 않습니다.

이 프로세스를 셀프 슬라이싱이라고 하며, m_self == NULL이고 METH_CALL_UNBOUND이 설정되지 않은 경우 함수에 셀프 슬라이싱이 있다고 합니다.

셀프 슬라이싱이 있는 METH_NOARGS함수는 사실상 self라는 하나의 인자를 가집니다. 마찬가지로 셀프 슬라이싱이 있는 METH_O함수는 두 개의 인자를 가집니다.

METH_PASS_FUNCTION

이 플래그가 설정되면 C 함수는 함수 자체(즉, base_function인스턴스)를 추가적인 첫 번째 인자로 받아 호출됩니다. 특별한 경우로 함수가 bound_method인 경우에는 해당 메서드의 기반 함수가 전달됩니다(단, 재귀적으로 적용되지는 않습니다. bound_methodbound_method를 감싸는 경우에도 __func__는 한 번만 적용됩니다).

예를 들어 일반적인 METH_VARARGS함수의 시그니처는 (PyObject *self, PyObject *args)입니다. METH_VARARGS | METH_PASS_FUNCTION을 사용하면 이는 (PyObject *func, PyObject *self, PyObject *args)가 됩니다.

METH_FASTCALL

이는 기존에 존재하지만 문서화되지 않은 플래그입니다. 이를 공식적으로 지원하고 문서화할 것을 제안합니다.

METH_KEYWORDS없이 플래그 METH_FASTCALL이 설정되면 ml_meth필드는 (PyObject *self, PyObject *const *args, Py_ssize_t nargs)인자를 받는 PyCFunctionFast타입입니다. 이러한 함수는 위치 인자만 받으며, 해당 인자는 길이가 nargs인 일반 C 배열 args로 전달됩니다.

플래그 METH_FASTCALL | METH_KEYWORDS가 설정되면 ml_meth필드는 (PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)인자를 받는 PyCFunctionFastKeywords타입입니다. 위치 인자는 길이가 nargs인 C 배열 args로 전달됩니다. 키워드 인자의 nargs위치에서 시작하여 해당 배열에 이어집니다. 키워드 인자의 (이름)는 kwnames에서 tuple로 전달됩니다. 예를 들어 위치 인자 3개와 키워드 인자 2개가 주어진다고 가정하십시오. 그러면 args는 길이가 3 + 2 = 5인 배열이고, nargs는 3이며, kwnames는 2-튜플입니다.

내장 함수의 자동 생성

Python은 확장 타입의 경우 PyTypeObject.tp_methods필드를 사용하고 모듈의 경우 PyModuleDef.m_methods필드를 사용하여 cfunction의 인스턴스를 자동으로 생성합니다. PyTypeObject.tp_methodsPyModuleDef.m_methods배열은 PyMethodDef구조체의 배열이어야 합니다.

확장 타입의 언바운드 메서드

바인딩되지 않은 메서드의 타입이 method_descriptor에서 cfunction으로 변경됩니다. 바인딩되지 않은 메서드로 나타나는 객체는 클래스의 __dict__에 나타나는 객체와 동일합니다. Python은 정의하는 클래스에 따라 __parent__속성을 자동으로 설정합니다.

모듈의 내장 함수

모듈의 함수인 경우 __parent__는 모듈로 설정됩니다. METH_BINDING플래그가 지정되지 않으면 __self__도 모듈로 설정됩니다(하위 호환성을 위해서입니다).

중요한 결과로, 이러한 함수는 속성으로 사용될 때 기본적으로 메서드가 되지 않습니다(base_function.__get__m_selfNULL일 때만 그렇게 합니다). 이를 버그로 볼 수도 있지만, 이는 하위 호환성을 위한 것입니다. python-ideas에 게시된 초기 글 [6]에서는 내장 함수의 이러한 잘못된 기능을 유지한다는 합의가 있었습니다.

그러나 특정 내장 함수나 새로 구현된 내장 함수에서 이를 가능하게 하기 위해 METH_BINDING플래그는 __self__의 설정을 방지합니다.

추가 변경 사항

새로운 타입 플래그

tp_flags에 사용할 새로운 PyTypeObject플래그가 추가됩니다: Py_TPFLAGS_BASEFUNCTION은 이 타입의 인스턴스가 base_function처럼 호출되고 메서드로 바인딩될 수 있는 함수임을 나타냅니다.

이는 Py_TPFLAGS_LIST_SUBCLASS와 같은 플래그와 다릅니다. 단순히 서브클래스라는 것 이상을 나타내기 때문입니다. 또한 __call____get__의 기본 구현도 나타냅니다. 특히 이러한 base_function의 서브클래스는 Calling base_function instances섹션의 구현을 따라야 합니다.

이 플래그는 base_function에서 tp_calltp_descr_get구현을 상속하는 확장 타입에 대해 자동으로 설정됩니다. 확장 타입은 호환되는 방식으로 __call__이나 __get__을 재정의하는 경우 이 플래그를 명시적으로 지정할 수 있습니다. Py_TPFLAGS_BASEFUNCTION플래그는 힙 타입에 절대로 설정해서는 안 됩니다. 안전하지 않기 때문입니다(힙 타입은 동적으로 변경될 수 있습니다).

C API 함수

관련 Python/C API 매크로와 함수를 몇 가지 나열합니다. 일부는 기존 함수(변경되었을 수도 있음)이고, 일부는 새로운 함수입니다:

  • int PyBaseFunction_CheckFast(PyObject *op): opPy_TPFLAGS_BASEFUNCTION이 설정된 클래스의 인스턴스이면 true를 반환합니다. 이 함수는 base_function내부에 접근하는 것이 의미 있는지 확인할 때 사용해야 하는 함수입니다.
  • int PyBaseFunction_Check(PyObject *op): opbase_function의 인스턴스이면 true를 반환합니다.
  • PyObject *PyBaseFunction_New(PyTypeObject *cls, PyCFunctionDef *ml, PyObject *self, PyObject *module, PyObject *parent): 주어진 데이터로 cls의 새 인스턴스를 생성합니다(base_function의 서브클래스여야 합니다).
  • int PyCFunction_Check(PyObject *op): opcfunction의 인스턴스이면 true를 반환합니다.
  • int PyCFunction_NewEx(PyMethodDef* ml, PyObject *self, PyObject* module): cfunction의 새 인스턴스를 생성합니다. 특수한 경우로, selfNULL이면 대신 self = Py_None으로 설정합니다(하위 호환성을 위해서입니다). self이 모듈이면 __parent__self로 설정됩니다. 그 외의 경우 __parent__NULL입니다.
  • 기존 PyCFunction_...PyMethod_함수 중 다수에 대해 base_function인스턴스에서 동작하는 새 함수 PyBaseFunction_...을 정의합니다. 기존 함수는 새 함수의 별칭으로 유지됩니다.
  • int PyFunction_Check(PyObject *op): opMETH_PYTHON플래그가 설정된 base_function의 인스턴스이면 true를 반환합니다(이는 opfunction의 인스턴스인지 확인하는 것과 동일합니다).
  • int PyFunction_CheckFast(PyObject *op): PyFunction_Check(op) && PyBaseFunction_CheckFast(op)와 동등합니다.
  • int PyFunction_CheckExact(PyObject *op): op의 타입이 function이면 참을 반환합니다.
  • PyObject *PyFunction_NewPython(PyTypeObject *cls, PyObject *code, PyObject *globals, PyObject *name, PyObject *qualname): 주어진 데이터로 cls의 새 인스턴스를 생성합니다(function의 서브클래스여야 합니다).
  • PyObject *PyFunction_New(PyObject *code, PyObject *globals): function의 새 인스턴스를 생성합니다.
  • PyObject *PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname): function의 새 인스턴스를 생성합니다.
  • PyObject *PyFunction_Copy(PyTypeObject *cls, PyObject *func): 주어진 function을 복사하여 cls의 새 인스턴스를 생성합니다(function의 서브클래스여야 합니다).

types 모듈의 변경

두 가지 타입이 추가됩니다. base_function에 해당하는 types.BaseFunctionTypedefined_function에 해당하는 types.DefinedFunctionType입니다.

그 외에는 types 모듈에 변경 사항이 없습니다. 특히 types.FunctionTypefunction을 나타냅니다. 그러나 실제 타입은 변경됩니다. 특히 types.BuiltinFunctionType은 더 이상 types.BuiltinMethodType과 동일하지 않습니다.

inspect 모듈의 변경

새 함수 inspect.isbasefunctionbase_function의 인스턴스인지 확인합니다.

inspect.isfunctiondefined_function의 인스턴스인지 확인합니다.

inspect.isbuiltincfunction의 인스턴스인지 확인합니다.

inspect.isroutineisbasefunction 또는 ismethoddescriptor인지 확인합니다.

NOTE: bpo-33261 [3]을 먼저 수정해야 합니다.

프로파일링

현재 sys.setprofile은 내장 함수에 대해 c_call, c_returnc_exception 이벤트를 지원합니다. 이러한 이벤트는 내장 함수를 호출하거나 내장 함수에서 반환할 때 생성됩니다. 반면 callreturn 이벤트는 함수 자체에서 생성됩니다. 따라서 callreturn 이벤트에는 변경할 사항이 없습니다.

더 이상 C 함수와 Python 함수 사이를 구분하지 않으므로 Python 함수에 대한 c_* 이벤트가 발생하지 않도록 해야 합니다. 이는 ml_flagsMETH_PYTHON 플래그가 설정된 경우 해당 이벤트를 생성하지 않음으로써 수행됩니다.

CPython이 아닌 구현

이 PEP의 대부분은 CPython에만 해당합니다. 다른 Python 구현에서 필요한 두 가지 변경 사항은 base_function 베이스 클래스와 function을 서브클래싱할 수 있다는 사실입니다. cfunctiondefined_function 클래스는 필요하지 않습니다.

일관성을 위해 base_function을 요구하지만 이에 대한 요구 사항은 설정하지 않습니다. 단순히 object의 복사본이어도 허용됩니다. 새로운 __parent__(및 __objclass__) 속성에 대한 지원은 필요하지 않습니다. defined_function 클래스가 없다면 types.DefinedFunctionTypetypes.FunctionType의 별칭이어야 합니다.

근거

기존 클래스를 간단히 변경하면 안 됩니까?

새로운 base_function 클래스를 도입하지 않고 기존 클래스를 유지하여 이 문제를 해결하려고 시도할 수 있습니다.

이는 더 간단한 해결책처럼 보일 수 있지만 그렇지 않습니다. 서로 다른 3개의 클래스인 function, builtin_function_or_methodmethod_descriptor에 대한 인트로스펙션 지원이 필요하기 때문입니다. 뒤의 두 클래스에 대해 “인트로스펙션 지원”이란 최소한 서브클래싱을 허용하는 것을 의미합니다. 하지만 성능을 잃고 싶지 않으므로 빠른 서브클래스 검사가 필요합니다. 그러려면 tp_flags에 새로운 플래그 두 개가 필요합니다. 또한 서브클래스에서 내장 함수에 대해 __get__을 허용하고 싶으므로, 내장 함수에도 LOAD_METHOD 오코드를 구현해야 합니다. 더 일반적으로는 많은 기능을 중복 구현해야 하며, 최종 결과는 훨씬 더 복잡한 코드가 될 것입니다.

내장 함수 서브클래스의 인트로스펙션이 __text_signature__와 어떻게 상호 작용할지도 명확하지 않습니다. 동일한 클래스에 서로 독립적인 두 종류의 inspect.signature 지원을 두는 것은 문제를 자초하는 것처럼 보입니다.

또한 이는 Motivation에서 언급된 내장 함수와 Python 함수 사이의 다른 차이점 일부를 해결하지 못합니다.

왜 __text_signature__는 해결책이 아닙니까?

내장 함수에는 __text_signature__라는 속성이 있으며, 이 속성은 함수의 시그니처를 일반 텍스트로 제공합니다. 기본값은 ast.literal_eval로 평가됩니다. 이 때문에 표준 Python 클래스 중 일부만 지원하며 임의의 Python 객체는 지원하지 않습니다.

또한 __text_signature__가 어떤 방식으로든 임의의 시그니처를 허용한다고 해도, 이는 인트로스펙션의 한 부분일 뿐입니다. 예를 들어 inspect.getsourcefile에는 도움이 되지 않습니다.

defined_function과 function의 비교

여러 곳에서 기존 function 클래스를 defined_function으로 대체할지, 아니면 새로운 function 클래스로 대체할지 결정해야 합니다. 이는 가장 가능성 높은 사용 사례를 고려하여 결정합니다.

  1. types.FunctionTypefunction을 가리킵니다. 해당 타입이 types.FunctionType(...)을 사용하여 인스턴스를 생성하는 데 사용될 수 있기 때문입니다.
  2. inspect.isfunction()defined_function을 가리킵니다. 인트로스펙션이 지원되는 클래스가 바로 이 클래스이기 때문입니다.
  3. C API 함수는 function을 가리켜야 합니다. defined_function의 다양한 속성이 어떻게 구현되는지는 지정하지 않기 때문입니다. 일반적으로 C 확장에서는 인트로스펙션을 수행할 이유가 없으므로 이것이 문제가 되지 않을 것으로 예상합니다.

이 PEP의 범위: 어떤 클래스가 관련됩니까?

이 PEP의 주된 동기는 함수 클래스를 수정하는 것이므로, 기존 클래스인 builtin_function_or_methodfunction을 통합하고자 합니다.

내장 함수와 메서드가 동일한 클래스를 사용하므로, 바운드 메서드도 포함하는 것이 자연스럽습니다. 또한 Python 함수에는 “언바운드 메서드”가 없으므로, 확장 타입에서 언바운드 메서드를 없애는 것이 타당합니다.

현재로서는 staticmethod, classmethodclassmethod_descriptor 클래스는 변경하지 않습니다. 이들을 base_function 클래스 계층에 넣고 classmethodclassmethod_descriptor를 통합하는 것은 분명 타당합니다. 그러나 이 PEP는 이미 충분히 규모가 크므로, 이는 향후 개선 가능 사항으로 남겨 둡니다.

확장 타입의 __init__이나 __eq__와 같은 슬롯 래퍼는 일반 메서드와 상당히 다릅니다. 또한 일반적으로 직접 호출하지 않는데, 보통 foo[i] 대신에 foo.__getitem__(i)를 작성하기 때문입니다. 따라서 이러한 항목은 이 PEP의 범위에서 제외됩니다.

Python에도 instancemethod클래스가 있으며, 이는 바운드 및 언바운드 메서드에 사용되었던 Python 2의 유물처럼 보입니다. 여전히 이에 대한 사용 사례가 있는지는 명확하지 않습니다. 어떤 경우에도 이 PEP에서 이를 다룰 이유는 없습니다.

TODO: instancemethod를 사용 중단해야 합니까? CPython 3.7 내에서는 전혀 사용되지 않는 것처럼 보이지만, 외부 패키지에서 사용할 수도 있습니다?

METH_STATIC 및 METH_CLASS는 처리하지 않습니다.

이 PEP에서는 METH_STATICMETH_CLASS플래그를 거의 참조하지 않습니다. 이러한 플래그는 Automatic creation of built-in functions에 의해서만 검사됩니다. staticmethod, classmethod 또는 classmethod_descriptor가 바인딩될 때(즉, __get__이 호출될 때), m_self != NULLbase_function 인스턴스가 생성됩니다. classmethod의 경우, 메서드가 바인딩된 클래스가 m_self이므로 이는 명확합니다. staticmethod의 경우에는 m_self에 임의의 Python 객체를 사용할 수 있습니다. 하위 호환성을 위해 확장 타입의 정적 메서드에는 m_self = __parent__을 선택합니다.

__self__의 base_function 내 위치

처음에는 bound_method와 대조하여 base_function__self__ 슬롯을 추가하는 것이 이상하게 보일 수 있습니다. 이 아이디어는 기존 builtin_function_or_method 클래스에서 가져왔습니다. 이를 통해 이 PEP에서 논의하는 다양한 함수 클래스에 대해 __call____get__의 단일 일반 구현을 사용할 수 있습니다.

또한 __self__를 모듈로 설정하는 기존 내장 함수를 쉽게 지원할 수 있습니다(예를 들어 sys.exit.__self__sys입니다).

__doc__의 두 가지 구현

base_function은 함수 독스트링을 지원하지 않습니다. 대신 cfunctionfunction클래스는 각각 독스트링을 처리하는 고유한 방식을 가지며, bound_method는 래핑된 함수에서 __doc__를 가져오기만 합니다.

cfunction의 경우, 독스트링은 텍스트 시그니처와 함께 PyMethodDef의 읽기 전용 ml_doc필드에 C 문자열로 저장됩니다. function의 경우, 독스트링은 수정 가능한 Python 객체로 저장되며 실제로 문자열일 필요는 없습니다. __doc__를 처리하는 이처럼 매우 다른 두 가지 방식을 통합하기는 어려워 보입니다. 하위 호환성을 위해 기존 구현을 유지합니다.

defined_function의 경우, __doc__가 구현되어야 하지만 그 방법은 지정하지 않습니다. 서브클래스는 cfunction과 동일한 방식으로 또는 구조체 멤버를 사용하거나 다른 방식으로 __doc__를 구현할 수 있습니다.

서브클래싱

PyCFunction_CheckPyMethod_Check에 대한 빠른 타입 검사를 가능하게 하기 위해 cfunctionbound_method의 서브클래싱을 허용하지 않습니다.

허용하지 않을 이유가 없으므로 다른 클래스들의 서브클래싱도 허용합니다. Python 모듈의 경우 서브클래싱할 수 있는 유일하게 관련된 클래스는 function입니다. 다른 클래스들은 어차피 인스턴스화할 수 없기 때문입니다.

tp_call 대체: METH_PASS_FUNCTION 및 METH_CALL_UNBOUND

새로운 플래그 METH_PASS_FUNCTIONMETH_CALL_UNBOUND는 이전에 사용자 지정 tp_call을 사용했던 경우를 지원하기 위한 것입니다. 이는 객체 호출을 위한 Python/ceval.c의 특수 빠른 경로 수를 줄입니다. Python 함수, 내장 함수 및 메서드 디스크립터를 별도로 처리하는 대신 단일 검사만 수행하게 됩니다.

tp_call의 시그니처는 본질적으로 METH_VARARGS | METH_KEYWORDS | METH_PASS_FUNCTION | METH_CALL_UNBOUND플래그가 적용된 PyBaseFunctionObject.m_ml.ml_meth의 시그니처입니다. 유일한 차이는 self인자가 추가된다는 점입니다. 따라서 기존 tp_call슬롯을 대신 base_function구현을 사용하도록 변경하기는 쉬울 것입니다.

C 함수가 __parent__와 같은 함수의 추가 메타데이터에 접근하기만 하면 되는 경우에는 METH_CALL_UNBOUND없이 METH_PASS_FUNCTION을 사용하는 것도 타당합니다. 이는 예를 들어 PEP 573을 지원하는 데 필요합니다. 기존 메서드를 METH_PASS_FUNCTION을 사용하도록 변환하는 작업은 간단합니다. C 함수에 추가 인자를 하나만 더하면 됩니다.

하위 호환성

이 PEP를 설계하는 동안 하위 호환성을 지나치게 깨뜨리지 않도록 각별히 주의를 기울였습니다. 잠재적으로 호환되지 않는 변경 사항의 대부분은 CPython 구현 세부 사항에 대한 변경이며, 이러한 세부 사항은 다른 Python 인터프리터에서도 어차피 다릅니다. 특히 PyPy에서 올바르게 실행되는 Python 코드는 이 PEP에서도 계속 작동할 가능성이 매우 높습니다.

staticmethod, functools.partial 또는 operator.methodcaller와 같은 표준 클래스와 함수는 전혀 변경할 필요가 없습니다.

types 및 inspect의 변경 사항

typesinspect에 제안된 변경 사항은 동작 변경을 최소화하기 위한 것입니다. 그러나 일부 사항이 변경되는 것은 불가피하며, 이로 인해 types 또는 inspect를 사용하는 코드가 중단될 수 있습니다. 예를 들어 Python 표준 라이브러리에서는 이 때문에 doctest모듈을 변경해야 합니다.

또한 다양한 종류의 함수를 입력으로 받는 도구는 새로운 함수 계층 구조와 사용자 지정 함수 클래스의 가능성을 처리해야 합니다.

Python 함수

Python 함수의 경우 본질적으로 아무것도 변경되지 않습니다. 이전에 존재하던 속성은 여전히 존재하며, Python 함수는 이전과 같이 초기화되고 호출되며 메서드로 변환될 수 있습니다.

function이라는 이름은 하위 호환성을 위해 유지합니다. python_function처럼 더 구체적인 이름으로 변경하는 것이 타당할 수도 있지만, 그러려면 문서와 테스트 모음을 성가시게 많이 변경해야 합니다.

모듈의 내장 함수

내장 함수의 경우에도 아무것도 변경되지 않습니다. 이러한 함수는 메서드로 바인딩되지 않는다는 기존 동작을 유지합니다. 이는 __self__가 모듈로 설정된다는 사실의 결과입니다.

내장 바운드 메서드 및 언바운드 메서드

내장 바운드 및 언바운드 메서드의 타입이 변경됩니다. 그러나 이러한 메서드를 호출하는 데에는 영향을 주지 않습니다. base_function.__call__의 프로토콜은 (특히 __objclass__처리와 self 슬라이싱을) 하위 호환성을 유지하도록 특별히 설계되었기 때문입니다. 이전에 존재했던 모든 속성(예: __objclass____self__)은 여전히 존재합니다.

새로운 속성

일부 객체에 새로운 특수 이중 밑줄 속성이 추가됩니다. 예를 들어, 새로운 속성 __parent__는 모든 내장 함수에 나타나며 모든 메서드에는 __func__ 속성이 추가됩니다. 이제 __self__가 Python 함수의 특수 읽기 전용 속성이 된 사실로 인해 [4]에서 문제가 발생했습니다. 그러나 일반적으로 많은 부분이 중단되지는 않을 것으로 예상합니다.

method_descriptor 및 PyDescr_NewMethod

method_descriptor클래스와 PyDescr_NewMethod 생성자는 사용 중단되어야 합니다. 이들은 더 이상 CPython 자체에서는 사용되지 않지만 여전히 지원됩니다.

2단계 구현

TODO: 이 섹션은 선택 사항입니다. 이 PEP가 수락되면 이 2단계 구현을 적용할지 여부를 결정해야 합니다.

위에서 언급했듯이, Changes to types and inspect로 인해 기존 코드 일부가 중단될 수 있습니다. 중단을 더욱 최소화하기 위해 이 PEP는 2단계로 구현될 수 있습니다.

1단계: 기존 클래스를 유지하되 베이스 클래스를 추가합니다.

처음에는 base_function클래스를 구현하고 이를 공통 베이스 클래스로 사용하되, 그 외에는 기존 클래스(단, 해당 구현은 제외)를 유지합니다.

이 제안에서는 클래스 계층 구조가 다음과 같이 변경됩니다.:

                      object
                         |
                         |
                  base_function
                 /       |     \
                /        |      \
               /         |       \
      cfunction          |     defined_function
       |     |           |         \
       |     |      bound_method    \
       |     |                       \
       |  method_descriptor       function
       |
builtin_function_or_method

리프 클래스인 builtin_function_or_method, method_descriptor, bound_methodfunction은 기존 클래스에 대응합니다(methodbound_method로 이름 변경).

모듈에서 자동으로 생성되는 함수는 builtin_function_or_method의 인스턴스가 됩니다. 확장 타입의 언바운드 메서드는 method_descriptor의 인스턴스가 됩니다.

method_descriptor클래스는 cfunction의 복사본이지만, __get__bound_method 대신 builtin_function_or_method를 반환한다는 점이 다릅니다.

builtin_function_or_method클래스는 bound_method와 동일한 C 구조를 가지지만 cfunction을 상속합니다. __func__ 속성은 필수가 아니며, method_descriptor를 바인딩할 때만 정의됩니다.

inspect 함수의 구현은 현재 상태로 유지합니다. 이 점과 기존 클래스를 유지한다는 점으로 인해 타입 검사를 수행하는 코드의 하위 호환성이 보장됩니다.

실제 DeprecationWarning을 표시하면 올바르게 작동하는 코드에 큰 영향을 미치므로, 사용 중단 사항은 문서에만 나타납니다. 또 다른 이유는 isinstance(x, t)를 호출할 때 경고를 표시하기가 어렵고(__instancecheck__를 조작하여 구현할 수는 있음), type(x) is t에 대해서는 불가능하기 때문입니다.

2단계

2단계는 이 PEP의 나머지 부분에서 실제로 설명하는 내용입니다. 구현 측면에서 보면 1단계에 비해 상대적으로 작은 변경입니다.

참조 구현

이 PEP의 대부분은 CPython에서 https://github.com/jdemeyer/cpython/tree/pep575 구현되었습니다.

해당 브랜치의 커밋에 대응하는 네 단계가 있습니다. 각 단계를 마치면 CPython은 대부분 작동하는 상태가 됩니다.

  1. base_function 클래스를 추가하고 cfunction의 서브클래스로 만듭니다. 이 단계에서 완전한 __call__ 프로토콜이 구현되므로, 단연 가장 큰 단계입니다.
  2. methodbound_method로 이름을 바꾸고 base_function의 서브클래스로 만듭니다. 확장 타입의 언바운드 메서드를 cfunction의 인스턴스로 변경하여, 확장 타입의 바운드 메서드도 bound_method의 인스턴스가 되도록 합니다.
  3. defined_functionfunction을 구현합니다.
  4. 표준 라이브러리와 테스트 스위트 등 Python의 다른 부분을 변경합니다.

부록: 현재 상황

NOTE: 이 섹션은 PEP의 초안 작성 기간에 더 유용하므로, PEP가 승인된 후에는 자유롭게 삭제하십시오.

참고로 CPython 3.7에 현재 존재하는 관련 클래스를 자세히 설명합니다.

관련된 각 클래스는 “고아” 클래스입니다(실질적인 서브클래스나 슈퍼클래스가 없습니다).

builtin_function_or_method: 내장 함수와 바운드 메서드

이들은 PyCFunction_Type 유형이며, PyCFunctionObject 구조를 가집니다.:

typedef struct {
    PyObject_HEAD
    PyMethodDef *m_ml; /* Description of the C function to call */
    PyObject    *m_self; /* Passed as 'self' arg to the C func, can be NULL */
    PyObject    *m_module; /* The __module__ attribute, can be anything */
    PyObject    *m_weakreflist; /* List of weak references */
} PyCFunctionObject;

struct PyMethodDef {
    const char  *ml_name;   /* The name of the built-in function/method */
    PyCFunction ml_meth;    /* The C function that implements it */
    int         ml_flags;   /* Combination of METH_xxx flags, which mostly
                               describe the args expected by the C func */
    const char  *ml_doc;    /* The __doc__ attribute, or NULL */
};

여기서 PyCFunction은 C 함수 포인터입니다(이 포인터에는 여러 형태가 있으며, 가장 기본적인 형태는 self*args라는 두 인자를 받습니다).

이 클래스는 함수와 바운드 메서드 모두에 사용됩니다. 메서드의 경우 m_self 슬롯은 객체를 가리킵니다.:

>>> dict(foo=42).get
<built-in method get of dict object at 0x...>
>>> dict(foo=42).get.__self__
{'foo': 42}

일부 경우에는 함수를 해당 함수를 정의한 모듈의 “메서드”로 간주합니다.:

>>> import os
>>> os.kill
<built-in function kill>
>>> os.kill.__self__
<module 'posix' (built-in)>

method_descriptor: 내장 언바운드 메서드

이들은 PyMethodDescr_Type 유형이며, PyMethodDescrObject 구조를 가집니다.:

typedef struct {
    PyDescrObject d_common;
    PyMethodDef *d_method;
} PyMethodDescrObject;

typedef struct {
    PyObject_HEAD
    PyTypeObject *d_type;
    PyObject *d_name;
    PyObject *d_qualname;
} PyDescrObject;

function: Python 함수

이들은 PyFunction_Type 유형이며, PyFunctionObject 구조를 가집니다.:

typedef struct {
    PyObject_HEAD
    PyObject *func_code;        /* A code object, the __code__ attribute */
    PyObject *func_globals;     /* A dictionary (other mappings won't do) */
    PyObject *func_defaults;    /* NULL or a tuple */
    PyObject *func_kwdefaults;  /* NULL or a dict */
    PyObject *func_closure;     /* NULL or a tuple of cell objects */
    PyObject *func_doc;         /* The __doc__ attribute, can be anything */
    PyObject *func_name;        /* The __name__ attribute, a string object */
    PyObject *func_dict;        /* The __dict__ attribute, a dict or NULL */
    PyObject *func_weakreflist; /* List of weak references */
    PyObject *func_module;      /* The __module__ attribute, can be anything */
    PyObject *func_annotations; /* Annotations, a dict or NULL */
    PyObject *func_qualname;    /* The qualified name */

    /* Invariant:
     *     func_closure contains the bindings for func_code->co_freevars, so
     *     PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
     *     (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).
     */
} PyFunctionObject;

Python 3에는 “언바운드 메서드” 클래스가 없습니다. 언바운드 메서드는 단순한 함수일 뿐입니다.

method: Python 바운드 메서드

이들은 PyMethod_Type타입이며 구조체는 PyMethodObject입니다.:

typedef struct {
    PyObject_HEAD
    PyObject *im_func;   /* The callable object implementing the method */
    PyObject *im_self;   /* The instance it is bound to */
    PyObject *im_weakreflist; /* List of weak references */
} PyMethodObject;

참고 자료