C言語でPython拡張モジュールを作成する手順

<!-------------------------------------------------------------------------------------------------------------------------->
----
### はじめに
<!-------------------------------------------------------------------------------------------------------------------------->

#### 目的
C言語のソースファイルから、Python拡張モジュールを作成するための手順を記載する。
Python拡張モジュールを作成するため、環境構築手順も記載する。

<!-------------------------------------------------------------------------------------------------------------------------->
----
### Python拡張モジュールの作成手順
<!-------------------------------------------------------------------------------------------------------------------------->

#### 概要
C言語のソースファイルからPython拡張モジュールを作成する手順は、以下3種類がある。
今時点では「手順③」によるPyhton拡張モジュールの作成が推奨されているが、開発環境のpythonのバージョン等により作成手順を選択する。

* C言語のソースファイルからPython拡張モジュールを作成する手順
	* 手順①：(python3.11以前)setup.pyで"distutils.core"モジュールを呼び出して、C言語のソースファイルをビルドする
	* 手順②：(python3.12以降)setup.pyで"setuptools"モジュールを呼び出して、C言語のソースファイルをビルドする
	* 手順③：(手順②から)"pyproject.toml"を用意して、"python -m build"コマンドからC言語のソースファイルをビルドする

<!-------------------------------------------------------------------------------------------------------------------------->
----
### 環境構築(手順①向け)：distutilsモジュールのインストール
<!-------------------------------------------------------------------------------------------------------------------------->

#### 前提条件
Pythonのバージョンが3.11より古い場合には、Pythonの"distutils"モジュールを利用することができる。

#### pipインストール
下記コマンドでpipをインストールする。
```sh:pipインストール
sudo apt install python3-pip
(略)

pip -V
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

pip list
(pipのモジュール一覧を表示)
Package               Version
--------------------- ----------------
apt-clone             0.2.1
apturl                0.5.2
async-timeout         4.0.1
(以下略)

$ sudo apt list --installed | grep python3-distutils

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
python3-distutils/jammy-updates,jammy-security,now 3.10.8-1~22.04 all [installed,automatic]
```

pipをインストールした場合に、apt側の依存関係により"python3-distutils"のモジュール(debパッケージ)もインストールされる。

<!-------------------------------------------------------------------------------------------------------------------------->
----
### ビルド手順(手順①向け)
<!-------------------------------------------------------------------------------------------------------------------------->
#### ビルド用のソースファイルの配置
事前にソースファイルを準備する。今回は以下の章にある「サンプルプログラム」のソースファイルを利用してビルドを行う。
srcディレクトリ配下に以下ファイルを配置する。

* [サンプルプログラム(共通コアファイル)](#サンプルプログラム共通コアファイル)
* [サンプルプログラム(手順①ファイル)](#サンプルプログラム手順ファイル)

(ファイル配置構成)
> src/
> 　├ outputmes.c
> 　├ setup.py

以下コマンドでpythonモジュールをビルドして、作成したモジュールのインストールを行う。
```sh:pythonモジュールのインストール(ビルド)
mkdir src
cd src
pwd
(ここに。上記サンプルプログラムのソースファイルを配置する)

ls -l
合計 0
-rw-rw-r-- 1 user user 0  3月 23 16:52 outputmes.c
-rw-rw-r-- 1 user user 0  3月 23 16:52 setup.py
```

#### pythonモジュールのビルド
以下コマンドによりビルドを行う。

```
sudo python3 setup.py build

(以下ファイルがビルドできたことを確認する)
/home/user/src/setup.py:1: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives
  from distutils.core import setup, Extension
running build
running build_ext
building 'mymesmod' extension
creating build
creating build/temp.linux-x86_64-3.10
x86_64-linux-gnu-gcc -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.10 -c outputmes.c -o build/temp.linux-x86_64-3.10/outputmes.o
creating build/lib.linux-x86_64-3.10
x86_64-linux-gnu-gcc -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 -Wl,-Bsymbolic-functions -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.10/outputmes.o -o build/lib.linux-x86_64-3.10/mymesmod.cpython-310-x86_64-linux-gnu.so

```

#### 実行確認
作成されたモジュールが存在するディレクトリに移動して、モジュールの有無を確認する。
```
$ cd build/lib.linux-x86_64-cpython-310/
$ ls -l
-rwxr-xr-x 1 root root 24896 Mar 30 09:22 mymesmod.cpython-310-x86_64-linux-gnu.so
```




ビルド後は以下手順で実行確認を行う。
* [モジュールの実行確認](#モジュールの実行確認)

<!-------------------------------------------------------------------------------------------------------------------------->
----
### 環境構築(手順②③向け)：setuptoolsモジュールのインストール
<!-------------------------------------------------------------------------------------------------------------------------->

#### 前提条件
Pythonのバージョンが3.12以降では、Pythonの"distutils"モジュールが利用できない。
その後継モジュールである"setuptools"をインストールする必要がある。

#### エラー回避策
エラー回避策として、以下の2つの追加手順がある。
* 追加手順(1)：venvを利用する
* 追加手順(2)："break-system-packages"オプションを利用して強制的にインストールする

<!-------------------------------------------------------------------------------------------------------------------------->
----
### 環境構築(手順②③向け)：setuptoolsモジュールのインストール/追加手順(1)venv利用
<!-------------------------------------------------------------------------------------------------------------------------->

#### Python仮想環境構成(venv環境)
Python仮想環境構成(venv環境)は、以下となる

> myproject(プロジェクトフォルダ)/
> 　├ myenv1(venv環境)/
> 　│　├ bin/
> 　│　│　└ activate(環境変数)
> 　│　└ src/
> 　├ myenv2/
> 　└ myenv3/

#### venvインストール

```
sudo apt install python3.12-venv
```

#### venvコマンドでPythonの仮想環境を作成

```
mkdir myproject
(上記は任意のディレクトリ名で可能)
cd myproject
python3 -m venv myenv1
(Python仮想環境用のディレクトリ名を入れる。名前は任意で可能)
```

#### Pythonの仮想環境の有効化(環境変数の呼び出し)

```
. ./myenv1/bin/activate
環境変数の呼び出し

(myenv1) user@server01:~/myproject$
(括弧の中身には設定したPython仮想環境の名前が入る)

pip list
```

以下コマンドを実行して、該当のPython仮想環境に必要ツールをインストールする。

```
python3 -m pip install --upgrade pip
python3 -m pip install setuptools
python3 -m pip install build
pip list
```

Pythonの仮想環境の使用を終了したい場合には、以下コマンドで仮想環境から抜けることができる。

```
deactivate
```

<!-------------------------------------------------------------------------------------------------------------------------->
----
### 環境構築(手順②③向け)：setuptoolsモジュールのインストール/追加手順(2)強制インストール
<!-------------------------------------------------------------------------------------------------------------------------->

#### 手順
pipコマンドでのインストール時に引数を指定して強制的にインストールを行う。
```
python3 -m pip install --upgrade pip --break-system-packages
python3 -m pip install setuptools --break-system-packages
python3 -m pip install build --break-system-packages
pip list
```

<!-------------------------------------------------------------------------------------------------------------------------->
----
### ビルド手順(手順②向け)
<!-------------------------------------------------------------------------------------------------------------------------->
#### ビルド用のソースファイルの配置
事前にソースファイルを準備する。今回は以下の章にある「サンプルプログラム」のソースファイルを利用してビルドを行う。
srcディレクトリ配下に以下ファイルを配置する。

* [サンプルプログラム(共通コアファイル)](#サンプルプログラム共通コアファイル)
* [サンプルプログラム(手順②ファイル)](#サンプルプログラム手順ファイル-1)

(ファイル配置構成)
> src/
> 　├ outputmes.c
> 　├ setup.py

#### pythonモジュールのビルド
setuptoolsモジュールのインストールが完了した場合には、以下コマンドでpythonモジュールをビルドして、作成したモジュールのインストールを行う。
コマンドは手順①と同じである。

```sh:pythonモジュールのインストール(ビルド)
. ./myenv1/bin/activate
環境変数の呼び出し
cd src
python3 setup.py build
```

ビルド後は以下手順で実行確認を行う。
* [モジュールの実行確認](#モジュールの実行確認)

<!-------------------------------------------------------------------------------------------------------------------------->
----
### ビルド手順(手順③向け)
<!-------------------------------------------------------------------------------------------------------------------------->

#### ビルド用のソースファイルの配置
事前にソースファイルを準備する。今回は以下の章にある「サンプルプログラム」のソースファイルを利用してビルドを行う。
srcディレクトリ配下に以下ファイルを配置する。

* [サンプルプログラム(共通コアファイル)](#サンプルプログラム共通コアファイル)
* [サンプルプログラム(手順③ファイル)](#サンプルプログラム手順ファイル-2)

(ファイル配置構成)
> src/
> 　├ outputmes.c
> 　├ setup.py
> 　└ pyproject.toml

```sh:ソースファイル配置
cd myenv1
mkdir src
cd src
pwd
(ここに上記のファイルを配置する)
ls -l

. ./myenv1/bin/activate
環境変数の呼び出し
```

#### ビルド実行
上記のソースファイルが配置された状態で、以下コマンドを実行してビルドを行う。
```sh:pythonモジュールのビルド
python3 -m build
```

ビルド後は以下手順で実行確認を行う。
* [モジュールの実行確認](#モジュールの実行確認)

<!-------------------------------------------------------------------------------------------------------------------------->
----
### モジュールの実行確認(手順①②③共通)
<!-------------------------------------------------------------------------------------------------------------------------->
#### モジュールの実行確認
以下コマンドで、想定の出力がされるかを確認する。

```py:モジュールの実行確認
python3
Python 3.10.12 (main, Jan 17 2025, 14:35:34) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import mymesmod
(テストモジュールのimportを行う)

>>> mymesmod.output_mes()
Hello, World!
(テスト用の「Hello, World!」が表示されることを確認する)

>>> quit()
```

<!-------------------------------------------------------------------------------------------------------------------------->
----
### サンプルプログラム
<!-------------------------------------------------------------------------------------------------------------------------->

#### 処理概要
pythonからの実行により、"Hello, World!"を表示させるプログラムです。

#### サンプルプログラム(共通コアファイル)

手順①から手順③までの共通のC言語ファイル
以下ファイル(ソースファイル)を、srcディレクトリに配置する。

```cpp:outputmes.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static void output_mes(){
    printf("Hello, World!\n");
}

static PyObject *pyc_output_mes(PyObject *self, PyObject *args){
    output_mes();
    Py_RETURN_NONE;
}

static PyMethodDef output_mes_methods[] = {
    {"output_mes", pyc_output_mes, METH_NOARGS, PyDoc_STR("output messages")},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef output_mes_mod = {
    PyModuleDef_HEAD_INIT,
    "mymesmod",
    PyDoc_STR("Test module for output messages"),
    0,
    output_mes_methods,
};

PyMODINIT_FUNC PyInit_mymesmod(){
    return PyModule_Create(&output_mes_mod);
}
```
#### サンプルプログラム(手順①ファイル)

以下の"setup.py"をsrcディレクトリに配置する。

```py:setup.py(distutils用)
from distutils.core import setup, Extension

setup(
    name='mymesmod',
    version='1.0.0',
    ext_modules=[
        Extension(
            name="mymesmod",
            sources=["outputmes.c"],
        ),
    ]
)
```

#### サンプルプログラム(手順②ファイル)

以下の"setup.py"をsrcディレクトリに配置する。

```py:setup.py(setuptools用_pyproject.tomlを利用しない場合)
from setuptools import setup, Extension

setup(
    name='mymesmod',
    version='1.0.0',
    ext_modules=[
        Extension(
            name="mymesmod",
            sources=["outputmes.c"],
        ),
    ]
)
```

#### サンプルプログラム(手順③ファイル)

以下の"pyproject.toml"と"setup.py"をsrcディレクトリに配置する。

```toml:pyproject.toml
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "mymesmod"
version = "1.0.0"
```

```py:setup.py(pyproject.tomlを利用する場合)
from setuptools import setup, Extension

setup(
    ext_modules=[
        Extension(
            name="mymesmod",
            sources=["outputmes.c"],
        ),
    ]
)
```

<!-------------------------------------------------------------------------------------------------------------------------->
----
### 公開情報サイト
<!-------------------------------------------------------------------------------------------------------------------------->

* (手順①関連)distutilsでビルド
	* [C言語でPythonのモジュール作ってみる](https://qiita.com/Kashiwara/items/2088ba011446637aa8f4)
	* [Cで作った関数をpythonで呼ぶ](https://qiita.com/nabion/items/594fb3316583130a636e)
	* [【Python C API入門】C/C++で拡張モジュール作ってPythonから呼ぶ](https://cpp-learning.com/python_c_api_step1/)
	* [眺めて覚えるC言語 その1 PythonからC言語を呼ぶ(hello world)](https://qiita.com/hiratarich/items/760921581e30e92bc983)
	* [眺めて覚えるC言語 その2 PythonからC言語を呼ぶ(引数)　string](https://qiita.com/hiratarich/items/253d5017d822785049c6)
* (手順②関連)setuptoolsインストール(distutils廃止について)
	* [python3.12へバージョンアップしたところ「japanize_matplotlib 」でModuleNotFoundError発生](https://qiita.com/ti104110/items/497c43b6c3db4c2afc72)
	* [ModuleNotFoundError: No module named 'distutils' の対処法](https://qiita.com/pitao/items/1740a62ddee797aed807)
	* [Python 3.12での「ModuleNotFoundError: distutilsが見つかりません」を解決する方法](https://openillumi.com/python-3-12-distutils-error-fix-guide/)
	* [distutils の廃止予定（Python 3.12）](https://qiita.com/kkdd/items/dab5e1670b6859ce1f25)
	* [Python3.12の新機能 (まとめ)](https://qiita.com/ksato9700/items/2eac0d2bd0d0dc125f13)
* (手順②関連)setuptoolsでビルド
	* [Python3でCのAPI](https://qiita.com/driller/items/c639bc1229c87def7f51)
	* [年末が近づくと Python/C API を無駄に使いたくなるので準備](https://qiita.com/fgshun/items/63a70c74c9b67e969311)
* (手順③関連)pyproject.tomlファイルを作成してビルド
	* [C API で Python 拡張モジュールをつくる](https://zenn.dev/sankantsu/articles/b7f04329ec730c)
	* [Python/C API で 素朴な二分探索木を書いてみる](https://qiita.com/fgshun/items/36187fc66e04cafd5883)
	* [yproject.tomlとsetup.cfgで宣言的パッケージング](https://qiita.com/gyu-don/items/833ceb2068f33a9a11e1)
	* [2022年版pyproject.tomlを使ったPythonパッケージの作り方](https://qiita.com/ieiringoo/items/4bef4fc9975803b08671)
* Pythonパッケージの管理
	* [Python パッケージング周辺の概念, 用語, ツールの整理](https://zenn.dev/sankantsu/articles/1cddd3c08582a1)
	* [pyproject.toml を書く](https://packaging.python.org/ja/latest/guides/writing-pyproject-toml/)
* pipの利用手順
	* [Pythonを使う上で欠かせないパッケージ管理システムpipの使い方](https://qiita.com/saitoxu/items/18e6a1c8c7840029147e)
	* [pipをインストールするための方法とは？pipの扱い方もあわせて解説](https://bizroad-svc.com/blog/pip-install/)
	* [Pythonのパッケージ（ライブラリ）のバージョンを確認](https://note.nkmk.me/python-package-version/)
	* [Python3 - pip をアップデートする方法](https://www.curict.com/item/5a/5ac72d2.html)
* "error: externally-managed-environment"について
	* [初めてのPythonで "error: externally-managed-environment" を解決する](https://qiita.com/toiee_kame/items/c3781abb53f385dae4f9)
	* [pipを実行時の「error: externally-managed-environment」の解決方法](https://zenn.dev/tamanegi/articles/cab4ceba142b4d)
* venvの利用手順
	* [venv:Python仮想環境管理](https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e)
	* [venvで手軽にPythonの仮想環境を構築しよう](https://qiita.com/shun_sakamoto/items/7944d0ac4d30edf91fde)


----
<!-------------------------------------------------------------------------------------------------------------------------->