표준 라이브러리 확장 모듈¶
이 절에서는 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에 따르면 운영 위원회의 특별 허가가 없는 한 새로운 확장 모듈은 동작하며 테스트를 거친 순수 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> 함수의 이름은 import 문에서 사용하는 모듈 이름 <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을 우선해서 사용해야 합니다.
내장 확장 모듈의 경우 *static* 마커 뒤에 다음 줄을 추가하여 Modules/Setup.bootstrap.in을 갱신합니다:
*static*
...
_foo _foo/_foomodule.c _foo/helper.c
...
구문은 <NAME> <SOURCES>입니다. 여기서 <NAME>은 import 키워드 문에서 사용하는 모듈 이름이고, <SOURCES>는 공백으로 구분된 소스 파일 목록입니다.
그 밖의 확장 모듈의 경우 *@MODULE_BUILDTYPE@* 마커 뒤, *shared* 마커 앞에 다음 줄을 추가하여 Modules/Setup.stdlib.in을 갱신합니다:
*@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입니다). 마커 뒤에는 소스 파일 목록이 옵니다.
확장 모듈을 공유 모듈로 빌드해야 한다면 @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
yes
true
false
missing
false
true or false
disabled
확장이
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 프로젝트 파일 갱신하기¶
여기서는 Windows에서 MSVC로 컴파일하기 위한 최소한의 단계를 설명합니다.
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
팁
make -jN을 사용하면 가능한 한 많은 CPU 코어를 활용해 컴파일 속도를 높일 수 있습니다. 여기서 N은 할애하려는 (그리고 메모리가 감당할 수 있는) CPU 코어 수입니다. 인자 없이 make -j를 사용할 때는 주의하십시오. 이렇게 하면 작업 수에 제한이 없어지며, 컴파일 과정에서 때때로 많은 메모리를 사용할 수 있습니다(LTO로 빌드할 때처럼).
make regen-configure는 configure 스크립트를 갱신합니다.configure 스크립트는 특정 버전의
autoconf를 사용하여 생성해야 합니다. 이를 위해regen-configure규칙의 기반이 되는 Tools/build/regen-configure.sh 스크립트에는 Docker 또는 Podman이 필요하며, 기본적으로 Podman을 사용한다고 가정합니다.팁
Docker 대신 Podman을 설치할 것을 권장합니다. Podman은 백그라운드 서비스가 필요하지 않으며, 경우에 따라
root사용자 소유의 파일이 생성되는 것을 방지합니다.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 안정 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이 아닌 구현에서도 쉽게 사용할 수 있습니다.