C++ PR

【Python C API入門】C/C++で拡張モジュール作ってPythonから呼ぶ -前編-

記事内に商品プロモーションを含む場合があります

こんにちは。

現役エンジニアの”はやぶさ”@Cpp_Learningです。

仕事の都合もあり「C言語 ⇒ C++ ⇒ Python」の順にプログラミング言語を習得しました。

最近は、PythonとC/C++両方を使って仕事をしています。

Pythonでプログラミングをしていると…

【やりたいこと】

  • 処理速度を向上させたい
  • 過去に作ったC/C++のソースコードをPythonから呼びたい

と思うことがあります。

いくつか方法はありますが”Python C API”を使うことで【やりたいこと】を実現できそうだったので、勉強してみました。

備忘録も兼ねて本記事を書きます。

Python C APIとは

”Python C API”を使うことで、PythonモジュールをC/C++のソースコードで作成することができます。

つまり『”numpy”のような”モジュール”をC/C++で自作できる』ということです↓

※myModuleが自作モジュール・hogeがメソッド

”Python C API”を使うことで、PythonモジュールをC/C++で自作できる

Python C APIによる作業の流れ

「C/C++でモジュールを自作してPythonで使う」までの流れを図解で説明します↓

Python C API
  1. Python C API(Python.h)を使いC/C++でモジュール”myModule”作成
  2. ビルドしてsoファイルラッパー)を生成
  3. soファイルをインストールしてpipで管理
  4. pipで管理している”myModule”をimportして*.pyで使う

という流れになります。

スタートがC/C++であることを除けば、Pythonでモジュールを自作する流れと同じですね。

pipを使わず、ビルドするだけでも”myModule”をimportして使うことは可能です。ただし、インストールしてpipで管理する方が楽だと思います。

スポンサーリンク

Python C APIによる自作モジュールの作成手順

C/C++でPythonモジュールを作成するには、Python C APIの使い方を習得する必要があります。

Python C APIのサンプルを5つ用意したので、このサンプルを使って『Python C APIを使ったコードの書き方』を説明していきます。

Sample名 引数 戻り値
py_hello なし なし
py_add int int
py_list_param リスト int
py_param_list int リスト
py_list_list リスト リスト

上記 表の引数・戻り値は以下の意味をもっています。

  • 引数:Pythonから受け取る”値”や”リスト”※1
  • 戻り値:Pythonに返す”値”や”リスト”※2

(※1)C/C++で処理したいパラメータと考えても良い
(※2)C/C++の処理結果と考えても良い

Python C API サンプル -py_hello-

最初は”Hello World””Python C API(Python.h)”の使い方を掴んでもらいます。

Sample名 引数 戻り値
py_hello なし なし
py_add int int
py_list_param リスト int
py_param_list int リスト
py_list_list リスト リスト

”最初の1歩”が大変ですが、一度理解できれば”Python C API(Python.h)”を使いこなせると思います!

なお、今回説明するソースコードは、以下の記事で紹介した”WSL”で動作確認しました。

VSCodeからWSLを使う
【WSL】Windows10とUbuntuとVSCodeで快適なプログラミング環境を構築Windows Subsystem for Linux(WSL)とVisual Studio Code(VSCode)で快適なプログラミング環境を構築する方法を説明します。C言語とC++とPythonなどのプログラミング言語を使う人にオススメの記事です。...

Python call C function

最初にPythonから呼ぶC/C++関数を作成します。

『雛形』は以下の通りです。

見慣れないコードだと思いますが、ポイントは以下の通りです。

  • 相対パス/絶対パスどちらでも構わないので、Python.hをインクルード
  • ”引数”と”関数”の型には”PyObject”を使う

Hello World

上記の『雛形』をベースに”Hello World”をターミナルに表示する関数を作成します。

解説は以下の通りです。

  • 相対パスでPython.hをインクルード
  • 関数名を”c_helloworld”とした
  • Pythonがこの関数を呼ぶとターミナルに”Hello World”と表示される
  • 引数(Pythonから受け取る値)には”args”を使う場合が多い
  • 戻り値(Pythonに戻す値)がない場合はPy_Noneを使う

注意点は以下の通りです。

  • Pythonから受け取る値がなくても引数が必要
  • 引数に”args”以外の名称を使っても良い
  • 上記のサンプルでは、戻り値に”0”やNULLではなくPy_Noneを使う
  • 戻り値がある場合はPy_None以外を使う

The Module’s Method Table and Initialization Function

次に、作成した関数(c_helloworld)をPython からどうやって呼ぶかを定義します。

これも先に『雛形』を紹介します↓

これも見慣れないコードなので、最初は戸惑うと思いますが、本記事のサンプルも含め複数のコードを書くと慣れると思います。

今回は”myModule”というモジュール名にし、メソッド名は”helloworld”としました。

このモジュールは”Python C APIのSample1”で、メソッドの処理内容は“Hello World”をターミナルに表示することでした。

引数がないため”METH_NOARGS”を採用しました。

  • ”メソッド名”と”C/C++関数名”を揃えなくても良い(揃えても良い)
  • 複数の”C/C++関数”を呼ぶ設定も可能。ただし、最後は{NULL}で終わる
  • 引数がない場合は”引数の指定フラグ”に”METH_NOARGS”を採用
  • 引数がある場合は”METH_NOARGS”以外を使う
  • 説明していない部分は別のサンプルでも基本的に書き換え不要な部分

Sample1 -py_hello-

最終的に完成した“py_hello.c”のコードは以下です。

【Python C API】Sample”py_hello.c”

ビルド&インストール

もう一度、「C/C++でモジュールを自作してPythonで使う」までの流れを確認します。

Python C API

先ほどソースコードの“py_hello.c”を作成したので、次はビルド&インストールを行います。

setup.py

Pythonモジュールのビルド&インストールには”setup.py”を使います。

“setup.py”の『雛形』は以下の通りです。

以下が“py_hello.c”用の”setup.py”です。

あまりポイントはありませんが、あえて挙げるなら以下の通りです。

  • ”pipの登録名”と”モジュール名”は揃えなくても良い(揃えても良い)
  • インストールするファイルの拡張子が*.cppでも良い

ビルド&インストールと動作確認

”setup.py”と”py_hello.c”を同じディレクトリに保存後、ターミナルからcdコマンドで保存先に移動し、以下のコマンドでビルド&インストールを行います。

python setup.py install

buildディレクトリが生成され、中に*.soが保存されていればビルド成功です。

続いて、以下のコマンドでpipで管理しているモジュール一覧を表示させます。

pip freeze

自作モジュール名(”myModule==1.0.0”)が表示されればインストール成功です。

以下のpythonソースコードを実行して、”Hello World”が表示できれば「Pythonから拡張モジュールを使う」動作確認完了です。

一連の流れを実施したときのターミナル画面は以下の通りです。

C/C++の自作モジュールをインストール

以上が”Python C API(Python.h)”の最もシンプルな使い方です。

引数や戻り値の違いでコードの書き方は変わりますが、『雛形』は同じものを使えます。
『雛形』ですから)

なので、以降からは「引数や戻り値の違いでコードがどう変わるのか?」を中心に説明します。

Python C API サンプル -py_add-

次は2変数の足し算”c = a + b”のサンプルを使って説明をします。

Sample名 引数 戻り値
py_hello なし なし
py_add int int
py_list_param リスト int
py_param_list int リスト
py_list_list リスト リスト

”Hello World”のサンプルと異なり、引数・戻り値があります。

Pure C/C++ function

冒頭に書いた通り過去に作ったC/C++のソースコードをPythonから呼びたいときがあります。

つまり、以下のような”C/C++関数”が作成済みなら、丸ごと使いたい!と考えます。

しかし、このままではPythonから呼ぶことができないので、”Python C API(Python.h)”を使ってラッパー関数を作成します。

Wrapped function

ラッパー関数といっても、作り方は”Hello World”のサンプルとあまり変わりません。

違いは引数・戻り値の有無だけなので、いきなりC/C++のコードを公開↓

説明の都合上、上記のC/C++関数(pyadd)を呼ぶPythonコードも先に公開↓

PythonとC/C++のコードを見比べながらポイントを説明していきます。

上記のコードを別画面に表示した状態で以下の説明を読むことをオススメします。

ポイント❶ -Wrapping-

復習ですが、Pythonから呼ばれる関数の型には”PyOject”を使いました。

つまり、Pythonが呼ぶC/C++関数は”pyadd”になります。

しかし、Pythonのコードを見るとピュアC/C++関数”c_add”を呼んでいるように見えます。

c = addModule.c_add(2, 3)

これも復習ですが、”メソッド名”と”C/C++関数名”は揃えなくても良いです。

なので、関数”pyadd”をメソッド”c_add”と定義することで、さもPythonがピュアC/C++関数”c_add”を呼んでいるようなコードを書くことができます。

以上を踏まえ、関数(pyadd)をPython からどうやって呼ぶかを定義したコードが以下です。

これで”c_add”をラッピングした関数”pyadd”を定義できました。

”Hello World”サンプルで説明していないポイントのみを書き出したものが以下です↓

  • C/C++関数”pyadd”をPythonモジュールのメソッド”c_add”と定義
  • メソッドの説明は省略できる
  • モジュール名は”addModule”と定義
  • サンプルを見比べ、モジュール名の変更により、書き換わる部分を確認

ポイント❷ -Python C APIによる引数・戻り値の扱い方-

次に引数・戻り値について説明します。

【Python側】
c = addModule.c_add(2, 3)

【C/C++側】
static PyObject* pyadd(PyObject* self, PyObject* args)

【Python側】で(2,3)の2つの数値を引き渡していますが、【C/C++側】では“args”のみで受け取ります。

つまり、ポイントは以下の通りです。

変数/リスト/文字列などの型に依存せず、かつ引数が複数の場合でも
”PtObject* args”を使う

ただし、“args”で受け取った値をC/C++で扱うには型を定義する必要があります。

そこで、PyArg_ParseTupleを使い、int型の引数として受け取るため”i”を使います。

int a, b, c;

if (!PyArg_ParseTuple(args, “ii”, &a, &b))

(※PyObject ⇒ int 変換)

戻り値の場合は、Py_BuildValueを使い、同じくint型なので”i”を使います。

return Py_BuildValue(“i”, c);

(※int ⇒ PyObject 変換)

もちろんint型以外も扱えます(一部引用)↓

Python C API公式ドキュメント

引用元:Python/C API Reference Manual

また、引数が複数ある場合は”ii”とすることで、int型の引数2つを表現できます。

他にも以下のような引数の組み合わせが考えられます↓

 Python C API公式リファレンス Python C API公式リファレンス

引用元:Extending and Embedding the Python Interpreter 

Python C APIによる引数・戻り値の扱い方を整理するとこうなります↓

【Python C API -引数-】

PyArg_ParseTuple『PyObject ⇒ int 』などC/C++で扱える型に変換

【Python C API -戻り値-】

Py_BuildValue『int ⇒ PyObject』などPyObjectに変換してPythonに返す

【Python C API -引数・戻り値 共通-】

Sample2 -py_add-

最終的に完成した“py_add.c”のコードは以下です。

【Python C API】Sample”py_add.c”

ビルド&インストールと動作確認

以下のsetup.pyでビルド&インストールを行います。

一連の流れを実施したときのターミナル画面は以下の通りです。

C/C++の自作モジュールをインストール
スポンサーリンク

まとめ -前編-

”Python C API”を用いて『C/C++でPythonの拡張モジュールを作成する方法』を説明しました。

Sample名 引数 戻り値
py_hello なし なし
py_add int int
py_list_param リスト int
py_param_list int リスト
py_list_list リスト リスト

↑表に示す5つのサンプルを用いて、”Python C API”の入門チュートリアル記事に仕上げる予定でしたが…

かなりのボリュームになってしまったので、前編/後編に記事を分けることにしました。

後編では”Python C API(Python.h)”によるリストの扱う方について、残り3つのサンプルを用いて説明します。

後編もお楽しみに!

はやぶさ
はやぶさ
理系応援ブロガー”はやぶさ”@Cpp_Learningは頑張る理系を応援します!

PICK UP BOOKS

  • 数理モデル入門
    数理モデル
  • Jetoson Nano 超入門
    Jetoson Nano
  • 図解速習DEEP LEARNING
    DEEP LEARNING
  • Pythonによる因果分析
    Python