ããã¸ã§ã¯ããCã½ã¼ã¹ã³ã¼ããPythonããã±ã¼ã¸ã§ä½¿ãã
- 0. æ¦è¦
- 1. C language project å®è£
- 2. CMake å®è£
- 3. Python ç¨ C è¨èª Wrapper å®è£
- 4. Python/C Wrapper ã® CMake å
- 5. Docker 㨠setup.py ã®æ´å
- 6. pyproject.toml ã®æ´å
- github.com
ï¼ï¼ï¼Python ç¨ C è¨èª Wrapper å®è£
å ã®ç« ã§ã¯ã CMake ã®ä½¿ãæ¹ãå¦ãã ã ããã§ã¯ã CMake ãä¸æ¦èã«ããã¦ã C è¨èªã§å®ç¾©ãããé¢æ°ã Python ããå¼ã³åºãæ¹æ³ã解説ããã
- ããã¸ã§ã¯ããCã½ã¼ã¹ã³ã¼ããPythonããã±ã¼ã¸ã§ä½¿ãã
- ï¼ï¼ï¼Python ç¨ C è¨èª Wrapper å®è£
- 1. 調æ»
- 2. ããã±ã¼ã¸ã¤ã³ã¹ãã¼ã«
- 3. ãã£ã¬ã¯ããªæ§æ
- 4. test å·¥ç¨
- 5. Python Wrapper ã³ã¼ãã£ã³ã°
- 5.1. mypkg ã® Wrapper: mypkg_wrap.c (ã³ã¼ãã®å ¨ä½æ§é )
- 5.2. mypkg_wrap.c (ã¡ã½ããå®è£ ç·¨)
- 5.3. mypkg_wrap.c (ã¡ã½ããå®ç¾©ç·¨)
- 5.4. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«å®ç¾©ã»ãã¾ããªãç·¨)
- 5.5. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«å®ç¾©ç·¨)
- 5.6. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«åæåç·¨)
- 5.7. mypkg_wrap.h (ãããç·¨)
- 6. build å·¥ç¨
- 7. test 確èª
- 8. ã¾ã¨ã
1. 調æ»
- everylittle, pytestã«å ¥éãã¦ã¿ãã¡ã¢ -- Qiita, 2018/01
- Python Software Foundation, Python 3 ã¸ã®æ¡å¼µã¢ã¸ã¥ã¼ã«ç§»æ¤ -- Python HOWTO, 2021/12
- Python Software Foundation, å¼æ°ã®è§£éã¨å¤ã®æ§ç¯ -- Python/C API ãªãã¡ã¬ã³ã¹ããã¥ã¢ã«, 2023/08
- Python Software Foundation, å ±éã®ãªãã¸ã§ã¯ãæ§é ä½ -- Python/C API ãªãã¡ã¬ã³ã¹ããã¥ã¢ã«, 2023/08
- Python Software Foundation, 循ç°åç §ã¬ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ããµãã¼ããã -- Python/C API ãªãã¡ã¬ã³ã¹ããã¥ã¢ã«, 2023/08
2. ããã±ã¼ã¸ã¤ã³ã¹ãã¼ã«
sudo apt install -y python3-dev python3-pip python3-venv pip install pytest pytest-sugar
3. ãã£ã¬ã¯ããªæ§æ
åºæ¬çã« 1-c_lang_project ãå¼ãç¶ãã
æ´æ°ç¹ã¯ããã¡ã¤ã«åæ«å°¾ã« (*)
ã¨ã¤ãã¦ããã
3-c_wapper_for_python âââ util.bash âââ build.bash âââ src â  âââ myapp â  â  âââ main.c â  âââ mypkg â  â  âââ include â  â  â  âââ mypkg.h â  â  âââ mypkg.c â  âââ mypkg_wrap (*) â  âââ include (*) â  â  âââ mypkg_wrap.h (*) â  âââ mypkg_wrap.c (*) âââ src-python (*) â  âââ mypkg (*) â  âââ __init__.py (*) â  âââ core.py (*) âââ test.bash âââ tests ã  âââ run_test.base.c ã  âââ run_test.base.h ã  âââ test_mypkg.c ã  âââ test_mypkg.py (*)
4. test å·¥ç¨
TDD ã«å¾ãã tests/test_mypkg.py ãä½ã£ã¦ãããã 以ä¸ã®ãã¹ããå®è¡ããããã¨ãããã®ç« ã®ç®çã¨ãªãã
import mypkg def test_run_mypkg_ret(): assert mypkg.message(0) == 0 def test_run_mypkg_out(capfd): mypkg.message(0) captured = capfd.readouterr() assert captured.out == "Hello world!\n"
å ã«ã pytest ã«ã¤ãã¦ç°¡åã«èª¬æãã¦ããã pytest ã¯ã Python ç¨ã®ãã¹ããã¬ã¼ã ã¯ã¼ã¯ã§ãè²ã ã¨ãã¹ãã®å®è¡ã便å©ã«ãã¦ãããã ä¾ãã°ã以ä¸ã®ãããªç¹å¾´ãããã
test_*.py
ã®ãã¡ã¤ã«ãåæã«è¦ã¤ãã¦ãã¦ãdef test_*()
ã§å®ç¾©ãããé¢æ°ããã¹ãé¢æ°ã¨èªèãã¦ããã¹ããå®è¡ãã¦ããããassert <bool>
ã¨ãªããããªæ¸ãæ¹ãããã ãã§ãããã¹ãã«ããã¦ããããã¹ããã¨ããã®ãä¸è²«ãã¦ç°¡æ½ã«æ¸ããã
ä¸ã®ä¾ã ã¨ã
test_run_mypkg_ret()
ã§ã¯ã mypkg.message() ã®è¿ãå¤ã 0 ã§ãããã¨ã確ããã¦ãããtest_run_mypkg_out(capfd)
ã§ã¯ãæ¨æºåºåã "Hello world!" ã¨ãªããã¨ã確ããã¦ããã- capfd ã¯æ¨æºåºåçã®ãã¡ã¤ã«ãã£ã¹ã¯ãªãã¿(fd)ããã£ããã£ã¼(capture)ããããã®ã pytest ãæä¾ãã¦ãã便å©ãªãã¸ã§ã¯ãã§ããã
ãã¦ãå®éã«ããããã£ã¬ã¯ããªã§ pytest
ã¨å®è¡ãã¦ã¿ããã
ãã¹ãéã£ã¦ããªãå ´åã¯ã python3 -m pytest
ã§å®è¡ã ã
$ pytest Test session starts (platform: linux, Python 3.11.4, pytest 7.3.1, pytest-sugar 0.9.7) rootdir: /tmp/3-c_wrapper_for_Python plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collecting ... ââââââââââââââââââââââââââââââââââââââââââââââââââ ERROR collecting tests/test_mypkg.py âââââââââââââââââââââââââââââââââââââââââââââââââââ ImportError while importing test module '/tmp/3-c_wrapper_for_Python/tests/test_mypkg.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: /usr/lib/python3.11/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) tests/test_mypkg.py:4: in <module> import mypkg E ModuleNotFoundError: No module named 'mypkg' collected 0 items / 1 error ========================================================= short test summary info ========================================================= FAILED tests/test_mypkg.py !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Results (0.04s):
pytest-sugar ãã¤ã³ã¹ãã¼ã«ãããªãããã°ãããæãã«è²ã¥ãã¦ããã¯ãã§ããã
å½ããåã ããä»ã®æç¹ã§ã¯ import mypkg
ãããã¦åããªãã
ã¨ã©ã¼ã¡ãã»ã¼ã¸ããããèªãããã« No module named 'mypkg'
ã¨æ¸ãã¦ããã
主ã«ããã解決ãã¦ããã®ããã®ç« ã®ç®çã§ããã CMake ã§èªå解決ãã¦ãããã®ã¯ãç°å¢ä¾åãé¿ããæå³ã§ãè¯ããã¨ã§ã¯ãããã åä½åçã確èªããæå³ã§ãã gcc ããä¸åº¦ãã«ããã¦ãããã
ç°å¢ä¾åã®ã³ã¼ãã«ãªãã ãããããå ã« wsl ã® Ubuntu 20.04 ç°å¢ã§ãããã¨ã注è¨ãã¦ããã ç°ãªãç°å¢ã®äººã§ãããããã«æ¸ãã¦ãããã¨ããã³ãºãªã³çã«ãããã¨ãã¦ãããªãã çºããã ãã«çãã¦ã第åç« ã«ç§»ããã¨ããå§ãããã
5. Python Wrapper ã³ã¼ãã£ã³ã°
ã¾ããWrapper (ã©ããã¼) ã¨ããæ¦å¿µãæ´çãããã Wrapper ã¨ã¯ãèªçæ¥ã®ã©ããã³ã°ã¨åãã§ããå ã¿è¾¼ããã¨ããæå³ã ã ä¾ãã°ãå®´ä¼ç¨ã®é¢¨è¹ãããã¬ãã²ã¼ã ãã Google Play ã®ããã³ã¼ããã å¤è¦ãå ¨é¨ãæ£æ¹å½¢ã®ããããªç®±ãã«ãã¦ãã¾ããã ããã§ã¯ããC è¨èªããã©ãããã¦ããPython è¨èªãã®ã³ã¼ãã«è¦ãããã¦ãã¾ãã
ãããã£ãç¹æ®ãªãã¨ãããé½åä¸ãå¤æ°ã®ããã¾ããªããã絡ãã§ãã¦ã æãããããã¯ç°å¢ä¾åã§ãããã¨ãå¤ãã¨æãããã®ã§ã 以éã®è§£èª¬ã«ã¯æ³¨æãã¦ã»ããã
é話ä¼é¡ã ãã®ãã©ãããããã¨ããå·¥ç¨ã«ã¯ãé常ã®ã©ããã³ã°ãããã§ããããã«ã次ã®3ã¤ã®ç»å ´äººç©ãåºã¦ããã
- å ã¿è¾¼ã¾ãããã®ï¼ C è¨èªã®ã³ã¼ã (libmypkg.so, mypkg_wrap.c)
- å ã¿è¾¼ãã å¾ã®ãã®ï¼ ä¸è¿°ã® C è¨èªã®ã³ã¼ãã Python ã«ããã©ã¤ãã©ãª (libmypkg_wrap.so)
- å ã¿è¾¼ãã å¾ã®ãã®ãåãåã人ï¼ä¸è¿°ã®ã©ã¤ãã©ãªãå¼ã³åºã Python ã³ã¼ã (mypkg.py)
ããã§ã¯ã第ä¸ç« ã§ä½æãã mypkg ã Python ã§å¼ã³åºããã¨ãç®æãã¦ã ãããã®ç»å ´äººç©ãé çªã«å®è£ ã»èª¬æãã¦ããã
5.1. mypkg ã® Wrapper: mypkg_wrap.c (ã³ã¼ãã®å ¨ä½æ§é )
ããã§ã¯ã src é ä¸ã« mypkg_wrap ãã£ã¬ã¯ããªãä½æãã mypkg_wrap.c ãä½æãããã
詳細ãªå®è£ é¨åãçãã°ãå ¨ä½ã¯ä»¥ä¸ã®ãããªæ§æã«ãªãã
#include "mypkg_wrap.h" #include "mypkg.h" /*********************************************************** * Body (ã©ããé¢æ°ã®å®ç¾©é¨å) ***********************************************************/ static PyObject *pywrap_message( PyObject *self, PyObject *args, PyObject *kw) { // ... } /*********************************************************** * Magic code (Python ã§å¼ã³åºããããã«ããããã®ããã¾ããªããé¨å) ***********************************************************/ // å®ç¾©ãã Python ã¡ã½ããã®ä¸è¦§ static PyMethodDef libmypkg_methods[] = { { "message", /* ... */, }, // message() ã¡ã½ããã // ... }; // ... // mypkg ã¢ã¸ã¥ã¼ã«ã®å®ç¾© static struct PyModuleDef moduledef = { // ... "libmypkg", // const char*: ã¢ã¸ã¥ã¼ã«åã // ... libmypkg_methods, // libmypkg ãå«ãã¢ã¸ã¥ã¼ã«ä¸è¦§ã // ... }; // mypkg ã® __init__ ã«å¯¾å¿ãã C è¨èªã³ã¼ã PyMODINIT_FUNC PyInit_libmypkg(void) { PyObject *module = PyModule_Create(&moduledef); // ... return module; }
é çªã«è§£èª¬ãããã ã¨ã¯ãããå ·ä½çãªä¸èº«ã¯å¾ã§è§£èª¬ããã¨ãã¦ãå ãã¯å ¨ä½ã®æ¦è¦ãæ´ããã¨åªå ããã
ã¾ãããããã¼ã¤ã³ã¯ã«ã¼ãã®é¨åã ãã mypkg ãã©ããããäºå®ãªã®ã§ã mypkg.h ãã¤ã³ã¯ã«ã¼ããã¦ããã mypkg_wrap.h ã«ã¯ããã¾ããªããããæ¸ããã æå³ã®ãããã¨ãæ¸ããã¨ã¯ç¡ãã®ã§ã説æãå¾åãã«ããã
#include "mypkg_wrap.h" #include "mypkg.h"
次ã«ãã©ããããé¢æ°ãå®ç¾©ããé¨åã ã
ä»å㯠libmypkg ã¨ããååã®ã¢ã¸ã¥ã¼ã«ã«ãªãã®ã§ã
python ã®ã¢ã¸ã¥ã¼ã«ã¨ãã¦ã¯ libmypkg.message()
ã¡ã½ãããå®ç¾©ãããã¨ã«ãªãã
static PyObject *pywrap_message( PyObject *self, PyObject *args, PyObject *kw) { // ... }
PyObject ã¨ããããã£ã½ãååã並ãã§ãã¦ãããã«ã Python ç¨ã³ã¼ãã¨ãã£ãæãã ã ãã®ã³ã¼ãã Python ã§æ¸ãã¨ãããªãã以ä¸ã®ããã«ãªãã
(libmypkg.py) def message(*args, **kw): # ...
次ã®é¨åã¯ãããã¾ããªããé¨åã ã Python ãªãã¡ã½ããã .py ã«æ¸ãããããã®ã¢ã¸ã¥ã¼ã«ã®ã¡ã½ããã¨ãã¦èªèãã¦ããããã C è¨èªã§ã¯ã¡ããã¨èªåã§å®ç¾©ãã¦ãããªããã°ãªããªãã
// å®ç¾©ãã Python ã¡ã½ããã®ä¸è¦§ static PyMethodDef libmypkg_methods[] = { { "message", (PyCFunction)pywrap_message,ã/* ... */, }, // message() ã¡ã½ããã // ... }; // ... // mypkg ã¢ã¸ã¥ã¼ã«ã®å®ç¾© static struct PyModuleDef moduledef = { // ... "libmypkg", // const char*: ã¢ã¸ã¥ã¼ã«åã // ... libmypkg_methods, // libmypkg ãå«ãã¡ã½ããä¸è¦§ã // ... };
å¤æ°ã®ãã¾ããªãã«ç´ããéè¦ãªé¨åã ããããã¯ã¢ãããã¦ããã
æåã«ã PyMethodDef libmypkg_methods[]
ã¯ã Body é¨åã§å®è£
ããåã
ã®ã¡ã½ãããã
Python ã§ã®ã¡ã½ããåãå«ãè¨å®å¤ã¨çµã«ãã¦å®ç¾©ãã¦ããæ
å ±ã®é
åã§ããã
ãã®ã¡ã½ããå®ç¾©é
åããä»åº¦ã¯ã¢ã¸ã¥ã¼ã«å®ç¾©å¤æ° struct PyModuleDef moduledef
ã«æ¸¡ãã
moduledef
ã¯ãã¢ã¸ã¥ã¼ã«ã®å®ç¾©ã¨ãã¦ãå°ãªãã¨ãã¢ã¸ã¥ã¼ã«ã®ååã¨ã
libmypkg_methods
ã§å®ç¾©ãããã¡ã½ãã群ãå«ããã¨ãåããã
æå¾ã«ããã® moduledef
ã Python ã® __init__()
ã¡ã½ããç¸å½ã®é¢æ°ã«æ¸¡ãã
// mypkg ã® __init__ ã«å¯¾å¿ãã C è¨èªã³ã¼ã PyMODINIT_FUNC PyInit_libmypkg(void) { PyObject *module = PyModule_Create(&moduledef); // ... return module; }
ããã§ã C è¨èªã®ã³ã¼ãã®ãä½æ³ã«åã£ã¦ã libmypkg
ã¢ã¸ã¥ã¼ã«ãå®ç¾©ã§ããã
å¾ã¯ gcc ãä¸æãããã° libmypkg.so ãã§ãã¦ã¤ã³ãã¼ãã§ããããã«ãªãã ããã¾ã§å ã«é²ã¿ãã人ã¯5ç¯ã«é²ããã
åã³ã¼ãã®å 容ã確èªãããå ´åã¯ãå¼ãç¶ã次ã®é ã«é²ãã§ã»ããã
5.2. mypkg_wrap.c (ã¡ã½ããå®è£ ç·¨)
C è¨èªã¨ãã¦ã® message()
é¢æ°ã®å®è£
ã¯æ¸ãã§ãã¦ã mypkg.c ã§å®è£
ããã¦ããã
ã§ã¯ããã ãããå¼ãã§æ¥ãã°ããã®ãã¨ããã¨ã
ãã®é¢æ°ã Python ã«è¦ããããä¸ã§è²ã
ã¨ãããªãã¨ãããªããã¨ãããã
- Python ã®åã®å¼æ°ãåãåãå¿ è¦ãããã
- è¿ãå¤ã Python ã®åã«ç´ãå¿ è¦ãããã
ãåç¥ã®éãã Python ã«ã¯åããªãã æ£ç¢ºã«ã¯ããã¹ã¦ã®å (ã¯ã©ã¹) ã Object åãç¶æ¿ãã¦ãã¦ã Object åã®å¤æ°ã¨ãã¦å¤ãããåãããã®ã§ã Python ã®å®è£ ã¯æ¥µãã¦æ¥½ã§ããã
ããããä¸èº«ã®å®è£ ã¾ã§ããã§ã¯ãªãã®ãå¨ç¥ã®ã¨ããã ã æåå㨠int ã®è¶³ãç®ã¯ã§ããªãã (æãç®ã¯ã§ããã®ããããããã¨ããã ã)ã re ã¨ãã®è¿ãå¤ãªãã¸ã§ã¯ãã®ã¡ã½ãããå¼ã³åºããã¨ãã¦ããã None ã ããã¨æããããªãã¦ããã£ã¡ã ãã§ããã
ä¸æ¹ãC è¨èªã¯åãå³å¯ã«æ±ããç¶æ¿ã¨ããæ¦å¿µããµãã¼ãããã¦ããªã (ããã¨ãããæå)ã å®è£ ã¯å¤§å¤ã ããéç解æãå¹ãåããã°ãå°ãªããªã£ã¦ããã¨ãã話ãè´ãã
ã¨ãããããã®å³å¯ãªå管çãè¡ã C è¨èªã®ãä½æ³ã«å¾ã£ã¦ã
Python ã®ãããããªå㧠message()
é¢æ°ã使ããããã«ã
æ§ã
ãªå¤æ°ã PyObject ã¸ã¨ã©ããã³ã°ãã¦ããå¿
è¦ãããã
ã¨ãããããã®ã©ããã³ã°ããã® Wrapper ã³ã¼ãã®ãã¹ã¦ã§ããã
static PyObject *pywrap_message( PyObject *self, PyObject *args, PyObject *kw) { long long res_for_py; /* [A] Python ã®ã¡ã½ããå®ç¾©ã¨å¯¾å¿ãã C è¨èªã®ã³ã¼ã */ int res_for_c; static char *keywords[] = {"res", NULL}; if (!PyArg_ParseTupleAndKeywords( args, kw, "i", keywords, &res_for_c)) { return NULL; /* Error on parse parameters on Python. */ } /* [B] C è¨èªã®ã³ã¼ãã®å®è¡é¨åã®æ¬ä½ */ res_for_py = (long long)message(res_for_c); /* [C] ã©ã³ã¿ã¤ã ã¨ã©ã¼æã®å¦ç (raise RuntimeError(...) ã¨å¯¾å¿) */ if (res_for_py < 0) { PyErr_SetString(PyExc_RuntimeError, "Error on message()."); return NULL; } /* [D] (Python ã®ã¡ã½ããã¨ãã¦ã®) è¿ãå¤ã®å¦ç */ return Py_BuildValue("L", res_for_py); }
å ã®ã³ã¼ãã«ã¯ç¡ãããå®è¡é¨åã«æ²¿ã£ã¦ã [A] ãã [D] ã®è¨å·ãæ¯ã£ãã åºæ¬çã«ã¯ã³ã¡ã³ãã«æ¦ç¥ãæ¸ãã¦ãããã以éã§ç°¡åã«è¨è¿°ãã¦ããã
5.2.1. [A] ã¡ã½ããå®ç¾©
[A] ã¯ã Python ã®ã¡ã½ããå®ç¾©ã¨å¯¾å¿ãã C è¨èªã®ã³ã¼ãã§ããã
ã¾ããkeywords ã«å¼æ°ã®ååãæ¸ãã¦ãããä»å㯠res ãããªãã®ã§ãããã ãæ¸ãã¦ããã
次ã«ã PyArg_ParseTupleAndKeywords ã§ã Python ããåãåã£ãå¼æ°ãã¼ã¿ããã¼ã¹ããã
keywords ã®å®£è¨ã¨ã "i"
ã¨ãã¦å®ç¾©ããã¦ããé¨åã対å¿ãã¦ãã¦ãåå¼æ°ã®åã表ãã
i
㯠int32
ãæå³ãã¦ãã¦ã res
å¼æ°ã int
åã§ãããã¨ãæå³ãã¦ããã
L
ã¨ãããã long long
ã¤ã¾ã int128
ã表ããã¨ã¨ãªãã
è¤æ°ã®å¼æ°ãããå ´åã¯ãåç´ã«æååãé·ããã¦ãããï¼ã¤ int ã®å¼æ°ããããªã ii
ã ã
ãªãã·ã§ã³å¼æ°ã®å ´åã¯ã i|i
ãæå®ããã
ã¾ãããã®å ´å㯠C è¨èªå´ã§åæå¤ãè¨å®ãã¦ãããªããã°ãªããªããã¨ã«æ³¨æããã
ä»åã®å ´åã§ä¾ããã¨ã int res_for_c
ã§ã¯ãªã
int res_for_c = 0;
ã¨ãã«ãã¦ãããªããã°ãªããªãã
5.2.2. [B] C è¨èªã³ã¼ãæ¬ä½
[B] ã¯ã C è¨èªã³ã¼ãæ¬ä½ã§ããã ããã§ã¯ mypkg.c ã® message() é¢æ°ãå¼ã³åºãã¦ãããã ããã«ç´æ¥å®è£ ãæ¸ãã¦ããã¡ããæ§ããªãã é¢æ°ãè¤éã«ãªãã®ã§ãå¥ã®é¢æ°ã«ãããããã¯ããæ¹ããããããããªããã ãããããã®è¦æ¨¡ãªãã©ã¤ãã©ãªã¾ã§ããããåããå¿ è¦ã¯ãªãã ããã
PyArg_ParseTupleAndKeywords
ã¯ã第ä¸ã»ç¬¬äºå¼æ°ã§ã
ååãªãå¼æ°args
ã¨ååä»ãå¼æ°kw
ãåãåãã
ãã®å¾ãå¼æ°ãã¨ã®åã¨ãå¼æ°ã®ååãç¶ãã¦æ¸ãã
è¿ãå¤ã¯æå¾ã«æ¸ãã¦ãå¤ãå
¥ãã¦ã»ããå¤æ°ã¸ã®ãã¤ã³ã¿ã¼ã渡ã(åç
§æ¸¡ã)ã
5.2.3. [C] ã©ã³ã¿ã¤ã ã¨ã©ã¼
[C] ã®ã©ã³ã¿ã¤ã ã¨ã©ã¼æã®å¦çã¯ãªãã·ã§ã³ã§ãããç¡ãã¦ãããã ã¾ããã¨ã©ã¼ã®ç¨®é¡ã«ãã£ã¦ããæè»ãªã¨ã©ã¼ãæãããã¨ãæ¤è¨ãããã
PyErr_SetString
ã«ã¯ãä¾å¤ã®åã¨ãã¨ã©ã¼ã¡ãã»ã¼ã¸ãå¼æ°ã¨ãã¦æ¸¡ãã
ããã§ã¯ã message ãè² ã®å¤ãè¿ãããã¨ã©ã¼ã¨ãããã¨ã«ãã¦ã
ãã®ã¨ã㯠RuntimeError ãæããããã«ãã¦ããã
ãã¡ããã Python å´ã® try-catch æ§æã§åãåããã¨ãã§ããã
5.2.4. [D] è¿ãå¤
[D] ã¯è¿ãå¤ã®å¦çã§ããã
Py_BuildValue
ã¯ãPyArg_ParseTupleAndKeywords
ã¨åæ§ã«ã
第ä¸å¼æ°ã«åãæååã¨ãã¦æ¸¡ãã¦ããã®å¾ã«(å¯å¤é·å¼æ°ã¨ãã¦)è¿ãå¤ã渡ãã
è¤æ°è¿ãããå ´åã¯ãåã«è¤æ°ã®æåã並ã¹ã¦ãè¿ãå¤ã対å¿ããæ°ã ã渡ãã°ããã
5.3. mypkg_wrap.c (ã¡ã½ããå®ç¾©ç·¨)
次ã«ãå®è£
ããã¡ã½ãããã libmypkg
ã¢ã¸ã¥ã¼ã«ã«ç»é²ãã¦ããä½æ¥ã«ç§»ã£ã¦ããã
å
ã«ç¤ºããéããå
ã㯠libmypkg_methods
ã«ã¡ã½ããå®ç¾©ãæ¾ãè¾¼ãã§ããã
// å®ç¾©ãã Python ã¡ã½ããã®ä¸è¦§ static PyMethodDef libmypkg_methods[] = { { "message", // ã¡ã½ããåã (PyCFunction)pywrap_message, // ã¡ã½ãããå¼ã³åºã C è¨èªã®é¢æ°åã (METH_VARARGS | METH_KEYWORDS), // ã¡ã½ããã®æ§é ãè¨å®ãããã©ã°ã // å¼æ°ã ããªã®ãããã¼ã¯ã¼ãå¼æ°ãåãã®ãçã "Show Hello world.", // docstringã }, {NULL, NULL, 0, NULL}, };
4ã¤ã®å¼æ°ãããã説æã¯ã³ã¡ã³ããåç §ã
第ä¸å¼æ°ã«ã¤ãã¦ã ãè£è¶³ãå¿ è¦ã¨æãããã METH_VARARGS ã METH_KEYWORDS 㯠Python.h ã§å®ç¾©ããããã©ã°å®æ°ã§ã ã¡ã½ããã®ååä»ãå¼æ°ã®æç¡çãå¶å¾¡ãããã©ã°ã«ãªã£ã¦ããã ãã©ã°å¶å¾¡ã¨ãããã¨ãããããèããããããããªãããåºæ¬ã¯2ãã¿ã¼ã³ãããªãã
METH_VARARGS
: ãã¼ã¯ã¼ãå¼æ°ããªãå ´å(METH_VARARGS | METH_KEYWORDS)
: ãã¼ã¯ã¼ãå¼æ°ãããå ´å
3.7 ããMETH_FASTCALL ã¨ããã®ã追å ããã¦ããããããã 使ã£ããã¨ãç¡ãã¦è§£èª¬ã§ããªãã®ã§ã説æãçç¥ãã (ããã誰ãæãã¦ãã ãã)ã
5.4. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«å®ç¾©ã»ãã¾ããªãç·¨)
循ç°åç §ã¬ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ããµãã¼ãããããã«ã¯ã 以ä¸ã®é¢æ°ãè¨å®ãã¦ããå¿ è¦ãããã
// 循ç°åç §ã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ç¨ã®é¢æ°è¨å® static int libmypkg_traverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->error); return 0; } static int libmypkg_clear(PyObject *m) { Py_CLEAR(GETSTATE(m)->error); return 0; }
ãã ãã GETSTATE ã¯ä»¥ä¸ã®ãã¯ãã§ãã (ããã®è¨è¿°ãè¸è¥²)ã
#define GETSTATE(m) ((struct module_state *)PyModule_GetState(m))
詳ããã¯èª¿æ»ã§ãã¦ããªããã 循ç°åç §ãèµ·ããã¨ã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ãä¸æãåããªãã ã¨ããã®ã¯ãç¥ã£ã¦ãã人ã¯ç¥ã£ã¦ããæåãªè©±ã ããã
ã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ãç¥ããªã人åãã®èª¬æã¯ããªããã ç°¡åã«è¨ãã°ãPython ã¯ã³ã¼ããç°¡åã«æ¸ããå©ä¾¿æ§ã売ããªä¸æ¹ã§ã 便å©ãã«èµ·å ããåé¡ãããã¨ã ãèªèãã¦ããã°ããã¨æãã
ãããåé¿ããããã®æ段ã¨ã®ãã¨ãªã®ã§ãå°å ¥ããªãæã¯ç¡ãã
5.5. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«å®ç¾©ç·¨)
ããããã¢ã¸ã¥ã¼ã«ã®å®ç¾©ã«ç§»ãã
// mypkg ã¢ã¸ã¥ã¼ã«ã®å®ç¾© static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "libmypkg", // const char*: ã¢ã¸ã¥ã¼ã«åã NULL, // const char*: docstringã sizeof(struct module_state), // Py_ssize_t: ã¢ã¸ã¥ã¼ã«ã®ãµã¤ãºã libmypkg_methods, // libmypkg ãå«ãã¡ã½ããä¸è¦§ã NULL, // ãå¤æ®µéåæåã®ããã®ã¹ãããå®ç¾©ã®é åãããããã // ä¸æ®µååæåãªã®ã§ NULLã libmypkg_traverse, // ä¸ã§å®ç¾©ããã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ç¨é¢æ°ã libmypkg_clear, // ä¸ã§å®ç¾©ããã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ç¨é¢æ°ã NULL, // ã¬ã¼ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ãã¢ã¸ã¥ã¼ã«ãªãã¸ã§ã¯ããéæ¾ããã¨ãã«å¼ã³åºãé¢æ°ã // ä»åã¯å¿ è¦ãªãã®ã§ NULLã };
ããã§ã¢ã¸ã¥ã¼ã«ã®æ§é ä½ãå®ç¾©ãã¦ããã
ãã ãPyModuleDef_HEAD_INIT
ã¨ããããã¾ããªããã®å¾ã«ã¢ã¸ã¥ã¼ã«åãæ¸ãã
ãã®å¾ãã»ã¼ããã¾ããªããã§åã¾ã£ã¦ãã¦ããã¾ã説æã§ããã¨ããããªãã
ã³ããããå ´åã«ã¯ mypkg
äºã
ã®ã¨ããã®ååã帰ãããã«æ°ãã¤ããããã¨ãããããããè¨ããã¨ã¯ãªãã
æ¦è¦ã¯ã³ã¡ã³ããåç §ã®ãã¨ã
5.6. mypkg_wrap.c (ã¢ã¸ã¥ã¼ã«åæåç·¨)
ãããã»ã¼ããã¾ããªãããã¼ãã§ããã
å
ç¨å®ç¾©ãã moduledef
ããªãããããã㦠return ããã
// mypkg ã® __init__ ã«å¯¾å¿ãã C è¨èªã³ã¼ã PyMODINIT_FUNC PyInit_libmypkg(void) { PyObject *module = PyModule_Create(&moduledef); if (module == NULL) { return NULL; // Erorr on __init__ process. } struct module_state *st = GETSTATE(module); st->error = PyErr_NewException("libmypkg.Error", NULL, NULL); if (st->error == NULL) { Py_DECREF(module); return NULL; // Erorr on __init__ process. } return module; }
ãªãããããã¨ããã®ã¯å¤§ä½ã¨ã©ã¼å¦çã§ã ã¨ã©ã¼å¦çãããªããªãã°ã以ä¸ã®ãããªå®ç¾©ã§ãè¯ããããä¸èº«ã¯ãã¾ããªãã
PyMODINIT_FUNC PyInit_libmypkg(void) { return PyModule_Create(&moduledef); }
5.7. mypkg_wrap.h (ãããç·¨)
åºæ¬çã«ã¯åèãã¼ã¸ãå ã«ã Python3 åãã®ä»¥ä¸ã®æ©è½ã®ã¿ãæ®ãã¦ããã
#ifndef __MYPKG_WRAP_HEADER__ #define __MYPKG_WRAP_HEADER__ #define PY_SSIZE_T_CLEAN #include <Python.h> struct module_state { PyObject *error; }; #define GETSTATE(m) ((struct module_state *)PyModule_GetState(m)) #endif
6. build å·¥ç¨
6.1. libmypkg_wrap.so ã®ä½æ
ãã¦ããããã build (gcc) ãã¦ããã gcc ã«ãã£ã¦ Python ããã¤ã³ã¯ã«ã¼ãå¯è½ãª libmypkg_wrap.so ãä½æãããã¨ãç®æ¨ã ãã ããã«ã¯ä»¥ä¸ã®ãã¡ã¤ã«ãå¿ è¦ã¨ãªãã
- libmypkg.so: libmypkg_wrap.so ãåç §ãããã¡ã¤ã«ã
- mypkg_wrap.c: å ç¨ä½æããã mypkg.so ã Python ç¨ã«ã©ãããã C è¨èªã³ã¼ãã
ã¾ããããããã®ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªãæå®ãã¦ããå¿ è¦ãããã
ããã¾ã§ã¯é常ã®ã³ã³ãã¤ã«ã¨åãã ããä»å㯠<Python.h>
ãã¤ã³ã¯ã«ã¼ãããããã
ãã®ã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªãæå®ããªããã°ãªããªãã
ããã¦ãã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªæ
å ±ã®è¡¨ç¤ºã¯ã python3 ã®æ¨æºæ©è½ã¨ãã¦æä¾ããã¦ããã
python3 -m sysconfig
ã§è¡¨ç¤ºãããæ
å ±ã®ä¸ã«å«ã¾ããã®ã ãã
INCLUDEPY
ã¨ããå¤æ°ãããã ã
$ python3 -m sysconfig | grep -E "\WINCLUDEPY\W" INCLUDEPY = "/usr/include/python3.11"
ã¨ãããã¨ã§ããããã¤ã³ã¯ã«ã¼ããã£ã¬ã¯ããªã«æå®ãã¦ããã
æçµçã«ã¯ãã«ã¬ã³ããã£ã¬ã¯ããªã src/mypkg_wrap ã«ç§»ãã 以ä¸ã® gcc ã³ãã³ããå®è¡ããã°è¯ãã
gcc -I ../mypkg/include -I ../mypkg_wrap/include -I /usr/include/python3.11 \
-shared -fPIC -o libmypkg_wrap.so \
mypkg_wrap.c ../mypkg/libmypkg.so
ããã§ã src/mypkg_wrap ãã£ã¬ã¯ããªé ä¸ã« libmypkg_wrap.so ãã§ããã
6.2. src-python/libmypkg.so ã®ä½æ㨠src-python/mypkg ã®å®è£
ãã¦ãã§ã¯ãlibmypkg_wrap.soããå®éã«å¼ã³åºã python ããã±ã¼ã¸ãå¿
è¦ã§ããã
ããã§ã¯æ¥µãã¦åç´ãªãã®ã¨ãã¦ã
src-python/mypkg
é
ä¸ã« Python ã³ã¼ããæ¸ãã¦
libmypkg_wrap.so ãå¼ã³åºãããã«ãããã
(C è¨èªã§ã Python ã§ã mypkg ã¨ããååã«ãã¦ãã¾ã£ãã®ã§ src ãã£ã¬ã¯ããªãåãããã
ããã®ååãè¡çªããªãéãã¯ã両æ¹ã¨ã src é
ä¸ã«å
¥ãã¦ãæ§ããªãã
ãã¡ããã C è¨èªã¨ Python ã§ãã£ã¬ã¯ããªãåããã®ãæãã ããã)
ã¾ãã cp ã³ãã³ããä½ãã§ã libmypkg_wrap.so ãã
src-python ãã£ã¬ã¯ããªé
ä¸ã« libmypkg.so ã¨ããååã§é
ç½®ããã
ååãå¤ããã®ã¯ã libmypkg
ã¨ããååã§ã¢ã¸ã¥ã¼ã«ãå®ç¾©ããããã§ãã
(éã«è¨ãã¨ã C è¨èªã©ã¤ãã©ãª mypkg ã¨ä¸¸ãã¶ãã®åå㧠Python ããã±ã¼ã¸ãä½ããã¨ãã¦è¦å´ãã¦ããâ¦)ã
Python ã¯ãã® libmypkg.so ãç´æ¥ã¤ã³ãã¼ãã§ãããå ·ä½çã«ã¯ã以ä¸ã®ããã«æ¸ãã°ããã
import libmypkg
Python ã®æ¤ç´¢ãã¹ãéã£ã¦ããéãã¯ãããã§åé¡ãªã mypkg ãã¤ã³ãã¼ãã§ããã
ããã§ããã£ã¨ libmypkg
ã¢ã¸ã¥ã¼ã«ã® message()
ã¡ã½ããã¨ãã¦ã
C è¨èªã©ã¤ãã©ãª mypkg
ã® message()
é¢æ°ãå¼ã³åºããããã«ãªã£ãã
ããã§ã¯ã以ä¸ã®ã³ã¼ãã core.py ã«æ¸ããã
import libmypkg def message(res): res = libmypkg.message(res) return res
ããã¦ã__init__.py
ã§ãããã¤ã³ãã¼ããããã¨ã§ã
mypkg.message()
ã®å½¢ã§ Python ã®ä»ã®ã³ã¼ãããå¼ã³åºããããã«ããã
from mypkg.core import message
é·ãã£ãããããã§ãã«ãåã³å®è£ ã¯å®äºã§ããã
7. test 確èª
ããã§ã¯ã4ç¯ã§ä½ã£ã pytest ç°å¢ãåå®è¡ãããã¨ã§ãæ¬ç« ããã¦ãããã
ããããã£ã¬ã¯ããªã§ pytest
ã¨å®è¡ããã°ãèªåçã«ãã¹ãã³ãã³ããå®è¡ãã¦ãããã®ã ã£ãã
$ pytest Test session starts (platform: linux, Python 3.11.4, pytest 7.3.1, pytest-sugar 0.9.7) rootdir: /tmp/3-c_wrapper_for_Python plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collecting ... ââââââââââââââââââââââââââââââââââââââââââââââââ ERROR collecting tests/test_mypkg.py âââââââââââââââââââââââââââââââââââââââââââââââââ ImportError while importing test module '/tmp/3-c_wrapper_for_Python/tests/test_mypkg.py'. Hint: make sure your test modules/packages have valid Python names. Traceback: /usr/lib/python3.11/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) tests/test_mypkg.py:4: in <module> import mypkg E ModuleNotFoundError: No module named 'mypkg' collected 0 items / 1 error ======================================================= short test summary info ======================================================= FAILED tests/test_mypkg.py !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Results (0.04s):
ãããå
ç¨ã¨åãã¨ã©ã¼ No module named 'mypkg'
ã§ããã
ãªãã ãããï¼
ããã¯ã Python ã®ã©ã¤ãã©ãªæ¤ç´¢ãã¹ãéã£ã¦ããªãããã ã libmypkg.so ã mypkg ãã src-python é ä¸ã«ããããã src-python ã¸ã¨ãã¹ãéããªããã°ãªããªãã
é常ã¯ã¢ã¸ã¥ã¼ã«ãç°å¢ã«ã¤ã³ã¹ãã¼ã«ãããã¨ã«ãã£ã¦ãããå®ç¾ãã¹ãã ãããã ãããªè©¦ä½ã¢ã¸ã¥ã¼ã«ãã¤ã³ã¹ãã¼ã«ã¯ããããªãã®ã§ã ããã§ã¯ PYTHONPATH ã®ç°å¢å¤æ°ã«ãã¹ã追å ãããã¨ã§å¯¾å¿ãããã
$ PYTHONPATH="$PYTHONPATH:./src-python" pytest Test session starts (platform: linux, Python 3.11.4, pytest 7.3.1, pytest-sugar 0.9.7) rootdir: /tmp/3-c_wrapper_for_Python plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collected 2 items tests/test_mypkg.py â 50% âââââ âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ test_run_mypkg_out ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ capfd = <_pytest.capture.CaptureFixture object at 0x7f05dce2c810> def test_run_mypkg_out(capfd): mypkg.message(0) captured = capfd.readouterr() > assert captured.out == "Hello world!\n" E AssertionError: assert '' == 'Hello world!\n' E - Hello world! tests/test_mypkg.py:14: AssertionError tests/test_mypkg.py ⨯ 100% ââââââââââ ======================================================= short test summary info ======================================================= FAILED tests/test_mypkg.py::test_run_mypkg_out - AssertionError: assert '' == 'Hello world!\n' Results (0.05s): 1 passed 1 failed - tests/test_mypkg.py:11 test_run_mypkg_out Hello world! Hello world!
ã©ããããã¹ã¯éã£ãããã ããã¨ã©ã¼ãçºçãã¦ãã¾ã£ãã
ã¨ã©ã¼ã®å 容ãè¦ãã¨ããæ¨æºåºåã« Hello world! ã¨è¡¨ç¤ºãããã¯ãããããã¦ããªããã¨ãããã®ã ã ã ãããã¹ãå¾ã« "Hello world!" ã¨2å表示ããã¦ãããã¨ã«ããã«æ°ã¥ãã ãããè¦ãã¨ã C è¨èªã® printf ã«ãããã¡ãªãããã¡ãªã³ã°åé¡ãçããããå¾ãªãã
ç´°ãã解説ã¯çããã C è¨èªã® printf é¢æ°ã¯ã printf ãå®è¡ããç¬éã«æ¨æºåºåã«æ¸ãè¾¼ã¾ããã®ã§ã¯ãªãã ä¸æçã«ãããã¡ãªã³ã°ããã¦ãã¦ã CPU ã空ããã¨ãçã«æ¸ãè¾¼ã¾ããä»æ§ã¨ãªã£ã¦ããã
ãããé²ãã§å¼·å¶çã«æ¸ãè¾¼ã¾ããã«ã¯ fflush(stdout)
ã¨ããé¢æ°ã使ãã
ãã®é¢æ°ã使ãããã«ãmypkg.c ãä¿®æ£ãã¦ããå¿
è¦ãããã
#include <stdio.h> int message(int res) { printf("Hello world!\n"); fflush(stdout); // Add this line. return res; }
ã§ã¯ãããä¸åº¦ãã¹ããå®è¡ãã¦ã¿ããã
$ PYTHONPATH="$PYTHONPATH:./src-python" pytest Test session starts (platform: linux, Python 3.11.4, pytest 7.3.1, pytest-sugar 0.9.7) rootdir: /tmp/3-c_wrapper_for_Python plugins: tap-3.3, anyio-3.6.2, sugar-0.9.7 collected 2 items tests/test_mypkg.py ââ 100% ââââââââââ Results (0.01s): 2 passed
ã¨ãããã¨ã§ãç¡äºãã¹ããéããã¨ã確èªã§ããã
8. ã¾ã¨ã
ããã§ã¯ã C è¨èªã Python ç¨ã«ã©ããããããã«æä½éå¿ è¦ãªã³ã¼ãã¨ã ããã¾ãæä½éå¿ è¦ãªãã«ãæ¹æ³ã«ã¤ãã¦å¦ãã ã
次ã¯ã2ç« ã¨3ç« ãçµã¿åããã¦ã CMake 㧠Python ç¨ C è¨èª Wrapper ãä½æãããã¨ãç®æãã