표준 라이브러리 확장 모듈

이 절에서는 C extension module을 사용하여 CPython 프로젝트를 구성하고 컴파일하는 방법을 설명합니다. C 확장 모듈을 작성하는 방법은 설명하지 않으며, 좋은 문서를 읽을 수 있는 몇 가지 링크를 제공합니다.

관련 datetime 또는 pickle 같은 표준 라이브러리의 일부 모듈에는 C와 Python으로 작성된 동일한 구현이 있으며, C 구현을 사용할 수 있는 경우 성능이 향상될 것으로 기대합니다(이러한 확장 모듈은 일반적으로 가속기 모듈이라고 합니다).

주로 Python으로 구현된 다른 모듈은 구현 세부 사항을 제공하는 C 도우미 확장을 임포트할 수 있습니다(예를 들어 csv 모듈은 Modules/_csv.c에 정의된 내부 _csv 모듈을 사용합니다).

확장 모듈 분류

확장 모듈은 두 범주로 분류할 수 있습니다.

  • 내장 확장 모듈은 Python 인터프리터와 함께 빌드되고 배포되는 모듈입니다. 내장 모듈은 인터프리터에 정적으로 연결되므로 __file__ 속성이 없습니다.

    더 보기

    sys.builtin_module_names — 내장 모듈의 이름입니다.

    내장 모듈은 Py_BUILD_CORE_BUILTIN 매크로가 정의된 상태로 빌드됩니다.

  • 공유 (또는 동적) 확장 모듈은 공유 라이브러리(.so 또는 .dll 파일)로 빌드되며 인터프리터에 동적으로 링크됩니다.

    특히 모듈의 __file__ 어트리뷰트에는 .so 또는 .dll 파일의 경로가 들어 있습니다.

    공유 모듈은 Py_BUILD_CORE_MODULE 매크로가 정의된 상태로 빌드됩니다. 대신 Py_BUILD_CORE_BUILTIN 매크로를 사용하면 모듈을 임포트할 때 ImportError가 발생합니다.

참고

비공식적으로 내장 확장 모듈은 필수 모듈로 간주할 수 있는 반면, 공유 확장 모듈은 외부에서 제공하거나 대체하거나 비활성화할 수 있다는 의미에서 선택 사항으로 간주할 수 있습니다.

일반적으로 가속기 모듈은 공유 확장 모듈로 빌드하며, 이미 순수 Python 구현이 있는 경우에는 특히 그렇습니다.

관련 PEP 399에 따르면 Steering Council의 특별 허가를 받지 않는 한 새로운 확장 모듈은 작동하며 테스트를 거친 순수 Python 구현을 반드시 제공해야 합니다.

CPython에 확장 모듈 추가하기

표준 라이브러리에 다음 foo.greet() 함수를 포함하는 순수 Python 모듈 foo가 있다고 가정합니다.

Lib/foo.py
def greet():
    return "Hello World!"

관련 foo.greet()의 Python 구현 대신 _foo 모듈에 공개된 해당 C 확장 구현을 사용하려고 합니다. 이상적으로는 Lib/foo.py를 다음과 같이 수정하려고 합니다.

Lib/foo.py
try:
    # use the C implementation if possible
    from _foo import greet
except ImportError:
    # fallback to the pure Python implementation
    def greet():
        return "Hello World!"

참고

가속기 모듈을 직접 임포트해서는 절대로 안 됩니다. 관례적으로 밑줄 접두사를 사용하여 비공개 구현 세부 사항임을 표시합니다(이 예에서는 _foo).

가속기 모듈을 통합하려면 다음 사항을 결정해야 합니다.

  • 확장 모듈 소스 코드를 CPython 프로젝트 트리의 어디에 추가할지,

  • CPython 프로젝트를 구성하고 컴파일하기 위해 어떤 파일을 수정할지, 그리고

  • 마지막에 어떤 Makefile 규칙을 호출할지 결정해야 합니다.

CPython 프로젝트 트리 업데이트

일반적으로 가속기 모듈은 CPython 프로젝트의 Modules 디렉터리에 추가합니다. 확장 모듈에 둘 이상의 파일이 필요한 경우 Modules 안에 하위 디렉터리를 만드는 편이 더 편리합니다.

확장 모듈이 파일 하나로 구성된 가장 단순한 예에서는 Modules 디렉터리에 Modules/_foomodule.c 경로로 배치할 수 있습니다. 단순하지 않은 _foo 확장 모듈의 예로 다음 작업 트리를 살펴봅니다.

관례상 확장 모듈 구현이 들어 있는 소스 파일의 이름은 <NAME>module.c이며, <NAME>은 나중에 임포트할 모듈의 이름입니다(여기서는 _foo). 또한 구현이 들어 있는 디렉터리의 이름도 이와 비슷하게 지정해야 합니다.

Modules/_foo/helper.h
#ifndef _FOO_HELPER_H
#define _FOO_HELPER_H

#include "Python.h"

typedef struct {
    /* ... */
} foomodule_state;

static inline foomodule_state *
get_foomodule_state(PyObject *module)
{
    void *state = PyModule_GetState(module);
    assert(state != NULL);
    return (foomodule_state *)state;
}

/* Helper used in Modules/_foo/_foomodule.c
 * but implemented in Modules/_foo/helper.c.
 */
extern PyObject *
_Py_greet_fast(void);

#endif // _FOO_HELPER_H

서로 다른 C 소스 파일 간에 공유할 필요가 없는 함수나 데이터는 libpython에서 해당 심벌이 내보내지는 것을 방지하도록 static으로 선언해야 합니다.

심벌을 내보내야 하는 경우 그 이름은 Py 또는 _Py로 시작해야 합니다. 이는 make smelly로 확인할 수 있습니다. 자세한 내용은 Python의 C API 변경하기 섹션을 참조하십시오.

Modules/_foo/helper.c
#include "_foomodule.h"

PyObject *_Py_greet_fast(void) {
    return PyUnicode_FromString("Hello World!");
}
Modules/_foo/_foomodule.c
#include "helper.h"
#include "clinic/_foomodule.c.h"

/* Functions for the extension module's state */
static int
foomodule_exec(PyObject *module)
{
    // imports, static attributes, exported classes, etc
    return 0;
}

static int
foomodule_traverse(PyObject *m, visitproc visit, void *arg)
{
    foomodule_state *st = get_foomodule_state(m);
    // call Py_VISIT() on the state attributes
    return 0;
}

static int
foomodule_clear(PyObject *m)
{
    foomodule_state *st = get_foomodule_state(m);
    // call Py_CLEAR() on the state attributes
    return 0;
}

static void
foomodule_free(void *m) {
    (void)foomodule_clear((PyObject *)m);
}

/* Implementation of publicly exported functions. */

/*[clinic input]
module foo
[clinic start generated code]*/
/*[clinic end generated code: output=... input=...]*/

/*[clinic input]
foo.greet -> object

[clinic start generated code]*/

static PyObject *
foo_greet_impl(PyObject *module)
/*[clinic end generated code: output=... input=...]*/
{
    return _Py_greet_fast();
}

/* Exported module's data */

static PyMethodDef foomodule_methods[] = {
    // macro in 'clinic/_foomodule.c.h' after running 'make clinic'
    FOO_GREET_METHODDEF
    {NULL, NULL}
};

static struct PyModuleDef_Slot foomodule_slots[] = {
    // 'foomodule_exec' may be NULL if the state is trivial
    {Py_mod_exec, foomodule_exec},
    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
    {0, NULL},
};

static struct PyModuleDef foomodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "_foo",
    .m_doc = "some doc",               // or NULL if not needed
    .m_size = sizeof(foomodule_state),
    .m_methods = foomodule_methods,
    .m_slots = foomodule_slots,
    .m_traverse = foomodule_traverse,  // or NULL if the state is trivial
    .m_clear = foomodule_clear,        // or NULL if the state is trivial
    .m_free = foomodule_free,          // or NULL if the state is trivial
};

PyMODINIT_FUNC
PyInit__foo(void)
{
    return PyModuleDef_Init(&foomodule);
}

PyInit_<NAME> 함수에는 임포트 문에 사용되는 모듈 이름 <NAME>(여기서는 _foo)을 접미사로 붙여야 하며, 이 이름은 일반적으로 PyModuleDef.m_name과 일치한다는 점을 기억하십시오.

관련 Argument Clinic 입력에서 사용하는 식별자 등에는 이러한 명명 요건이 없습니다.

CPython 프로젝트 구성

이제 CPython 소스 트리에 확장 모듈을 추가했으므로 여러 플랫폼에서 CPython 프로젝트를 컴파일할 수 있도록 일부 구성 파일을 업데이트해야 합니다.

Modules/Setup.{bootstrap,stdlib}.in 업데이트

확장 모듈이 정상적으로 작동하는 인터프리터를 만드는 데 필요한지에 따라 Modules/Setup.bootstrap.in 또는 Modules/Setup.stdlib.in 파일을 업데이트합니다. 전자의 경우 확장 모듈은 반드시 내장 확장 모듈로 빌드됩니다.

가속기 모듈에는 Modules/Setup.bootstrap.in 대신 Modules/Setup.stdlib.in 파일을 사용하는 편이 좋습니다.

내장 확장 모듈의 경우 Modules/Setup.bootstrap.in 파일에서 *static* 마커 뒤에 다음 줄을 추가하여 업데이트하십시오.

*static*
...
_foo _foo/_foomodule.c _foo/helper.c
...

구문은 <NAME> <SOURCES>이며, 여기서 <NAME>import 문에서 사용하는 모듈 이름이고 <SOURCES>는 공백으로 구분된 소스 파일 목록입니다.

그 밖의 확장 모듈의 경우 Modules/Setup.stdlib.in 파일에서 *@MODULE_BUILDTYPE@* 마커 뒤이자 *shared* 마커 앞에 다음 줄을 추가하여 업데이트하십시오.

*@MODULE_BUILDTYPE@*
...
@MODULE__FOO_TRUE@_foo _foo/_foomodule.c _foo/helper.c
...
*shared*

@MODULE_<NAME_UPPER>_TRUE@<NAME> 마커에서는 <NAME_UPPER><NAME>의 대문자 형태여야 하며, <NAME>은 앞에서와 같은 의미입니다(이 경우 <NAME_UPPER><NAME>은 각각 _FOO_foo입니다). 마커 뒤에는 소스 파일 목록이 이어집니다.

확장 모듈을 shared 모듈로 빌드해야 하는 경우 @MODULE__FOO_TRUE@_foo 줄을 *shared* 마커 뒤에 넣으십시오.

...
*shared*
...
@MODULE__FOO_TRUE@_foo _foo/_foomodule.c _foo/helper.c

관련 configure.ac 업데이트

  • SRCDIRS 변수를 찾아 다음 줄을 추가하십시오.

    AC_SUBST([SRCDIRS])
    SRCDIRS="\
    ...
    Modules/_foo \
    ..."
    

    참고

    이 단계는 CPython 프로젝트에 새 소스 디렉터리를 추가할 때만 필요합니다.

  • PY_STDLIB_MODPY_STDLIB_MOD_SIMPLE 사용 부분이 포함된 섹션을 찾아 다음 줄을 추가하십시오.

    dnl always enabled extension modules
    ...
    PY_STDLIB_MOD_SIMPLE([_foo], [-I\$(srcdir)/Modules/_foo], [])
    ...
    

    PY_STDLIB_MOD_SIMPLE 매크로가 받는 인자는 다음과 같습니다.

    • 관련 import 문에서 사용하는 모듈 이름 <NAME>,

    • 컴파일러 플래그(CFLAGS), 그리고

    • 링커 플래그(LDFLAGS).

    호스트 구성에 따라 확장 모듈을 활성화하거나 지원하지 못할 수 있다면, 대신 PY_STDLIB_MOD 매크로를 사용하십시오. 이 매크로는 다음을 인자로 받습니다:

    • 관련 import 문에서 사용하는 모듈 이름 <NAME>,

    • 확장이 활성화되었는지를 나타내는 불리언,

    • 확장이 지원되는지를 나타내는 불리언,

    • 컴파일러 플래그(CFLAGS), 그리고

    • 링커 플래그(LDFLAGS).

    예를 들어 Linux 플랫폼에서 _foo 확장을 활성화하되 32비트 아키텍처만 지원하려면 다음과 같이 합니다:

    PY_STDLIB_MOD([_foo],
                  [test "$ac_sys_system" = "Linux"],
                  [test "$ARCH_RUN_32BIT" = "true"],
                  [-I\$(srcdir)/Modules/_foo], [])
    

    보다 일반적으로, 확장의 호스트 구성 상태는 다음과 같이 결정됩니다:

    활성화됨

    지원됨

    상태

    true

    true

    true

    false

    누락

    false

    true or false

    비활성화됨

    확장이 PY_STDLIB_MOD_SET_NA 매크로에 의해 사용할 수 없는 것으로 표시되면 확장 상태는 n/a입니다. 확장을 사용할 수 없는 것으로 표시하려면 configure.ac에서 PY_STDLIB_MOD_SET_NA의 사용 위치를 찾아 다음 줄을 추가하십시오:

    dnl Modules that are not available on some platforms
    AS_CASE([$ac_sys_system],
        ...
        [PLATFORM_NAME], [PY_STDLIB_MOD_SET_NA([_foo])],
        ...
    )
    

외부 빌드 종속성이 필요한 확장 모듈에 새 종속성을 추가하는 방법은 configure.ac에 있는 기존 모듈의 주석과 구성을 참고하십시오.

관련 Makefile.pre.in 업데이트

필요한 경우 모듈 종속성 섹션에 다음 줄을 추가하십시오:

##########################################################################
# Module dependencies and platform-specific files
...
MODULE__FOO_DEPS=$(srcdir)/Modules/_foo/helper.h
...

MODULE_<NAME_UPPER>_DEPS 변수에는 @MODULE_<NAME_UPPER>_TRUE@<NAME> 마커와 동일한 명명 요구 사항이 적용됩니다.

MSVC 프로젝트 파일 업데이트

MSVC를 사용하여 Windows에서 컴파일하는 데 필요한 최소 단계를 설명합니다.

  • 관련 PC/config.c를 업데이트하십시오:

    ...
    // add the entry point prototype
    extern PyObject* PyInit__foo(void);
    ...
    // update the entry points table
    struct _inittab _PyImport_Inittab[] = {
       ...
       {"_foo", PyInit__foo},
       ...
       {0, 0}
    };
    ...
    

    _PyImport_Inittab의 각 항목은 가져올 모듈 이름(여기서는 _foo)과 접미사가 올바르게 붙은 해당 PyInit_* 함수로 구성됩니다.

  • 관련 PCbuild/pythoncore.vcxproj를 업데이트하십시오:

    <!-- group with header files ..\Modules\<MODULE>.h -->
    <ItemGroup>
      ...
      <ClInclude Include="..\Modules\_foo\helper.h" />
      ...
    </ItemGroup>
    
    <!-- group with source files ..\Modules\<MODULE>.c -->
    <ItemGroup>
      ...
      <ClCompile Include="..\Modules\_foo\_foomodule.c" />
      <ClCompile Include="..\Modules\_foo\helper.c" />
      ...
    </ItemGroup>
    
  • 관련 PCbuild/pythoncore.vcxproj.filters를 업데이트하십시오:

    <!-- group with header files ..\Modules\<MODULE>.h -->
    <ItemGroup>
      ...
      <ClInclude Include="..\Modules\_foo\helper.h">
        <Filter>Modules\_foo</Filter>
      </ClInclude>
      ...
    </ItemGroup>
    
    <!-- group with source files ..\Modules\<MODULE>.c -->
    <ItemGroup>
      ...
      <ClCompile Include="..\Modules\_foo\_foomodule.c">
        <Filter>Modules\_foo</Filter>
      </ClCompile>
      <ClCompile Include="..\Modules\_foo\helper.c">
        <Filter>Modules\_foo</Filter>
      </ClCompile>
      ...
    <ItemGroup>
    

헤더 파일은 <ClInclude> 태그를 사용하는 반면, 소스 파일은 <ClCompile> 태그를 사용합니다.

CPython 프로젝트 컴파일

이제 구성이 완료되었으므로 프로젝트를 컴파일하기만 하면 됩니다:

make regen-configure
./configure
make regen-all
make regen-stdlib-module-names
make

가능한 한 많은 CPU 코어를 활용하여 컴파일 속도를 높이려면 make -jN을 사용하십시오. 여기서 N은 할애할 수 있는 CPU 코어 수이자 메모리가 허용하는 수입니다. 인자 없이 make -j를 사용할 때는 작업 수에 제한이 없어 컴파일에 많은 메모리가 사용될 수 있으므로 주의하십시오(LTO로 빌드하는 경우 등).

  • make regen-configureconfigure 스크립트를 업데이트합니다.

    관련 configure 스크립트는 특정 버전의 autoconf를 사용하여 생성해야 합니다. 이를 위해 regen-configure 규칙의 기반이 되는 Tools/build/regen-configure.sh 스크립트에는 Docker 또는 Podman이 필요하며, 기본적으로 후자를 가정합니다.

    Podman은 백그라운드 서비스가 필요하지 않고 일부 경우에 root 사용자가 소유하는 파일이 생성되는 것을 방지하므로 Docker 대신 Podman을 설치하는 것이 좋습니다.

  • make regen-all은 헤더 파일을 다시 생성하고 Argument Clinic 같은 다른 스크립트를 호출합니다. 어떤 파일을 업데이트해야 하는지 모르는 경우 이 규칙을 실행하십시오.

  • make regen-stdlib-module-names는 표준 모듈 이름을 업데이트하여 _foo를 검색할 수 있게 하고 import _foo를 통해 임포트할 수 있게 합니다.

  • 이전의 make 호출로 프로젝트가 완전히 다시 빌드될 수 있으므로 마지막 make 단계는 일반적으로 필요하지 않지만, 일부 특정한 경우에는 필요할 수 있습니다.

문제 해결

이 절에서는 확장 모듈을 추가하는 이 예제를 따를 때 발생할 수 있는 일반적인 문제를 다룹니다.

regen-configure 대상을 만드는 규칙이 없음

이는 일반적으로 make distclean을 실행한 후 발생합니다(이 명령은 Makefile을 제거합니다). 해결 방법은 다음과 같이 configure 스크립트를 다시 생성하는 것입니다.

./configure            # for creating the 'Makefile' file
make regen-configure   # for updating the 'configure' script
./configure            # for updating the 'Makefile' file

관련 configure 스크립트가 없다면 Tools/build/regen-configure.sh를 실행하여 다시 생성할 수 있습니다.

./Tools/build/regen-configure.sh     # create an up-to-date 'configure'
./configure                          # create an up-to-date 'Makefile'

make regen-configure 및 Docker 권한 누락

Docker에서 권한이 없다고 보고하는 경우 다음 Stack Overflow 게시물이 문제 해결에 도움이 될 수 있습니다. How to fix docker: permission denied 또는 Podman을 사용해 볼 수 있습니다.

내부 헤더 사용 시 Py_BUILD_CORE 정의 누락

기본적으로 CPython Stable ABI#include "Python.h"를 통해 노출됩니다. 일부 경우에는 이것만으로 충분하지 않아 Include/internal의 내부 헤더가 필요하며, 특히 이러한 헤더를 사용하려면 Py_BUILD_CORE 매크로가 정의되어 있어야 합니다.

이를 위해 확장 모듈이 내장 모듈인지 공유 모듈인지에 따라 Py_BUILD_CORE_BUILTIN 또는 Py_BUILD_CORE_MODULE 매크로를 정의해야 합니다. 두 매크로 중 하나를 사용하면 Py_BUILD_CORE도 정의되며 CPython 내부에 접근할 수 있습니다.

관련 Py_BUILD_CORE_BUILTIN 정의
#ifndef Py_BUILD_CORE_MODULE
#  define Py_BUILD_CORE_BUILTIN 1
#endif
관련 Py_BUILD_CORE_MODULE 정의
#ifndef Py_BUILD_CORE_BUILTIN
#  define Py_BUILD_CORE_MODULE 1
#endif

이 절에서는 표준 라이브러리에 포함될 확장 모듈의 품질을 개선하기 위한 몇 가지 팁을 제공합니다.

제한된 API로 한정하기

CPython 이외의 구현에서도 새 확장 모듈의 이점을 활용할 수 있도록 제한된 API를 사용하는 것이 좋습니다. 전체 안정 ABI를 노출하는 대신 #include "Python.h" 지시문 앞에 Py_LIMITED_API 매크로를 정의하십시오.

3.13 제한된 API를 사용합니다.
#include "pyconfig.h"    // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
#  define Py_LIMITED_API 0x030d0000
#endif

#include "Python.h"

이렇게 하면 CPython 내부에 대한 의존성이 제거되어 확장 모듈을 비-CPython 구현에서도 쉽게 사용할 수 있습니다.