표준 라이브러리 확장 모듈¶
이 절에서는 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가 있다고 가정합니다.
def greet():
return "Hello World!"
관련 foo.greet()의 Python 구현 대신 _foo 모듈에 공개된 해당 C 확장 구현을 사용하려고 합니다. 이상적으로는 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 확장 모듈의 예로 다음 작업 트리를 살펴봅니다.
Modules/_foo/_foomodule.c — 확장 모듈 구현입니다.
Modules/_foo/helper.h — 확장 도우미 선언입니다.
Modules/_foo/helper.c — 확장 도우미 구현입니다.
관례상 확장 모듈 구현이 들어 있는 소스 파일의 이름은 <NAME>module.c이며, <NAME>은 나중에 임포트할 모듈의 이름입니다(여기서는 _foo). 또한 구현이 들어 있는 디렉터리의 이름도 이와 비슷하게 지정해야 합니다.
#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 변경하기 섹션을 참조하십시오.
#include "_foomodule.h"
PyObject *_Py_greet_fast(void) {
return PyUnicode_FromString("Hello World!");
}
#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_MOD및PY_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-configure는 configure 스크립트를 업데이트합니다.관련 configure 스크립트는 특정 버전의
autoconf를 사용하여 생성해야 합니다. 이를 위해regen-configure규칙의 기반이 되는 Tools/build/regen-configure.sh 스크립트에는 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 매크로를 정의하십시오.
#include "pyconfig.h" // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030d0000
#endif
#include "Python.h"
이렇게 하면 CPython 내부에 대한 의존성이 제거되어 확장 모듈을 비-CPython 구현에서도 쉽게 사용할 수 있습니다.