こんにちは。
現役エンジニアの”はやぶさ”@Cpp_Learningです。
前回以下の記事を書きました。
本記事は【前編】の続きで『C/C++でPythonの拡張モジュールを作成する方法』を説明します。
『setup.pyによるビルド&インストール』など前回説明済みの内容は割愛しますので、【前編】を読んでから本記事を読むことをオススメします。
Contents
Python C APIによる自作モジュールの作成手順
Python C APIのサンプルを5つ用意し、その内の2つは【前編】で説明しました。
今回は残り3つのサンプルを使って『Python C APIを使ったコードの書き方』を説明します。
Sample名 | 引数 | 戻り値 |
py_hello | なし | なし |
py_add | int | int |
py_param_list | int | リスト |
py_list_param | リスト | int |
py_list_list | リスト | リスト |
より具体的には、”Python C API(Python.h)”によるリストの扱い方について、残り3つのサンプルを用いて説明します。
上記 表の引数・戻り値は以下の意味をもっています。
- 引数:Pythonから受け取る”値”や”リスト”※1
- 戻り値:Pythonに返す”値”や”リスト”※2
(※1)C/C++で処理したいパラメータと考えても良い
(※2)C/C++の処理結果と考えても良い
Python C API サンプル -py_param_list-
最初に『pythonから数値を受け取り、C/C++で数値を2倍にし、リスト化して返す』サンプルについて説明します。
Sample名 | 引数 | 戻り値 |
py_hello | なし | なし |
py_add | int | int |
py_param_list | int | リスト |
py_list_param | リスト | int |
py_list_list | リスト | リスト |
引数はpy_addと同じ”int”ですが、戻り値は”リスト”になっています。
get param and return list
戻り値が”リスト”でも【前編】で説明した『雛形』を参考にC/C++コードを作成できます↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include "../../../include/python3.6m/Python.h" // C function "get param and return list" static PyObject* c_param_list(PyObject* self, PyObject* args) { int a, b; PyObject* c_list; // Decide variable type (int, int) if (!PyArg_ParseTuple(args, "ii", &a, &b)){ return NULL; } // multiplication a = a * 2; b = b * 2; // make python list (length 2) c_list = PyList_New(2); // set param PyList_SET_ITEM(c_list, 0, PyLong_FromLong(a)); PyList_SET_ITEM(c_list, 1, PyLong_FromLong(b)); return c_list; } |
解説は以下の通りです。
- C/C++コードで”PyObject* c_list”を定義
- c_listをリストとして扱うため”PyList_New”を使用
- ”PyList_New(n)”で長さnのリストを生成(今回は2変数を格納)
- ”PyList_SET_ITEM”でリスト”c_list”にパラメータをセット
- c_listの型がPyObjectなので、そのままreturn
順番にポイントを説明します。
ポイント❶ -PyList_SET_ITEM-
C/C++でリストに何かを格納する場合、”PyList_SET_ITEM”を使います。
PyList_SET_ITEM(リスト名, リストのi番目, 格納するもの);
ここでいう”格納するもの”にはPyObject型を使います。
ポイント❷ -Integer Objects(long ⇒ PyObject)-
リスト”c_list”の0番目に整数aを格納する場合は以下の通りです。
int a = 2;
PyList_SET_ITEM(c_list, 0, PyLong_FromLong(a));
”PyLong_FromLong”を使うことで『long(整数) ⇒ PyObject』に変換しています。
ここで注意すべき点は以下の通りです。
整数aが”int”でも”PyLong_FromLong”を使う
理由は公式リファレンスに書かれています↓
All integers are implemented as “long” integer objects of arbitrary size.
(すべての整数は任意の長さをもつ “long” 整数として実装されます。)引用元:Integer Objects
つまり「PyObjectで整数を扱う場合、longとして扱われる」という意味です。
Sample3 -py_param_list-
最終的に完成した“py_param_list.c”のコードは以下です。
※setup.pyと動作確認用のpythonコードも↑にあるので【前編】を参考に動かしてみて下さい。
Python C API サンプル -py_list_param–
次は『pythonからリストを受け取り、C/C++でリストに格納された数値の合計を算出して返す』サンプルについて説明します。
Sample名 | 引数 | 戻り値 |
py_hello | なし | なし |
py_add | int | int |
py_param_list | int | リスト |
py_list_param | リスト | int |
py_list_list | リスト | リスト |
今度は引数が”リスト”で戻り値が”int”です。
get list and return param
引数が”リスト”でも【前編】で説明した『雛形』を参考にC/C++コードを作成できます↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include "../../../include/python3.6m/Python.h" // C function "get list and return param" static PyObject* c_list_param(PyObject* self, PyObject* args) { int n; long a, sum = 0; // int a, sum = 0; PyObject* c_list, *item; // Decide variable type (list) if (!PyArg_ParseTuple(args, "O", &c_list)){ return NULL; } // Check list if PyList_Check(c_list){ // get length of the list n = PyList_Size(c_list); }else{ return NULL; } // Calculate list sum for (int i = 0; i < n; i++){ item = PyList_GetItem(c_list, i); a = PyLong_AsLong(item); sum = sum + a; Py_DECREF(item); } Py_DECREF(c_list); return Py_BuildValue("l", sum); // return Py_BuildValue("i", sum); } |
解説は以下の通りです。
- Pythonからリストを受け取る入れ物として”PyObject* c_list”を定義
- PyArg_ParseTupleでリストを受け取るために”O”を使う
- ”PyList_Check”でリストかどうかを確認
- ”PyList_Size”でリストの長さを取得
- ”PyList_GetItem”でリスト”c_list”に格納された値を取得
- 参照カウントを管理する
順番にポイントを説明します。
ポイント➌ -PyList_GetItem-
C/C++でリストに格納されたものを取得する場合、”PyList_GetItem”を使います。
取得したもの = PyList_GetItem(リスト名, リストのi番目);
ここでいう”取得したもの”にはPyObject型を使います。
リスト”c_list”の0番目を取得する場合は以下の通りです。
PyObject* item;
item = PyList_GetItem(c_list, 0);
ポイント❹ -Integer Objects(PyObject ⇒ long)–
【前編】含め、ここまでの内容で“PyObject”があらゆる型に対応できることが分かると思います。
また、“PyObject”をC/C++で扱うには、C/C++で扱える型に変換することも分かると嬉しい。
PyObject* item;
item = PyList_GetItem(c_list, 0);
”PyList_GetItem”で取得した”item”の型も”PyObject”なので、型を変換する必要があります。
今回は”item”を『PyObject ⇒ long(整数)』に変換するため、”PyLong_AsLong”を使います。
long a;
a = PyLong_AsLong(item);
”PyLong_FromLong”と使い方が似ているので、一度整理します。
【PyLong_FromLongの使い方】
”PyLong_FromLong”で『 long(整数)⇒ PyObject』に変換
【PyLong_AsLongの使い方】
”PyLong_AsLong”で『PyObject ⇒ long(整数)』に変換
【PyLong_FromLongとPyLong_AsLong共通】
- PyObjectの整数はlong型として扱われる
- 『int(整数)⇒ PyObject』の変換は『long(整数)⇒ PyObject』として扱われる※
※プラットフォームの違いなどにより、long以外は扱えない可能性があります
私の環境では”int a”でリストの数値を受け取り、”int sum”をreturnしても問題ありませんでしたが、念のため”int”をコメントアウトして”long”を採用しました。
ポイント➎ -参照カウント-
最後のポイントとして、参照カウントについて説明します(これが一番難しい)
“PyObject”でリスト/数値/文字列に対応できるのは、『動的にメモリを確保』しているからです。
動的に確保したメモリは、不要になった時点で解放しないとメモリーリークが発生し、システムに深刻なエラーが発生する危険性があります。
(感覚的な説明をすると、上図の”item”が拡大し続け、重要なメモリ領域を浸食するイメージ!)
Pythonには、不要になったメモリ領域を自動的に解放するガベージコレクションという仕組みが備わっているため、基本的には動的メモリを管理する必要はありません。
しかし、Python C APIを使う場合は、手動で動的メモリの管理を行う必要があります。
参照カウントとは
Python C APIを使う場合、参照カウントで動的メモリを管理します。参照カウントを図解で説明します↓
動的にメモリを確保する”item”を参照…つまり、itemの中身を確認する度に参照カウント(reference count)を増やしていきます。
上図の例では、参照カウントが”3”なので、3箇所から参照していることになります。
参照カウントのポイントは『参照カウントが”0”かどうか?』です。
↑は『itemの中身を参照⇒A/B/Cに代入⇒合計値sumを算出』するイメージです。
sumが算出できた時点(あるいは”C”にitemの中身を代入した時点)でitemの役割が無くなるなら、itemのメモリを解放する必要があります。
『どこからもitemを参照していない=参照カウント”0”』という意味なので…
”reference count = 3”を”reference count = 0”にすることが、メモリ解放の合図になります。
逆に言えば、”reference count ≠ 0”ならメモリを解放してはいけません!
動的メモリは参照カウントで管理でき、参照カウントが”0”になったときにメモリを解放します。
Python C APIと参照カウント
Python C APIで参照カウントを増減させるには、以下の関数を使います。
ただし、上記の関数を使わずに自動的に参照カウントを増減させる関数があります。
改めて”py_list_param”のC/C++コードを確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include "../../../include/python3.6m/Python.h" // C function "get list and return param" static PyObject* c_list_param(PyObject* self, PyObject* args) { int n; long a, sum = 0; // int a, sum = 0; PyObject* c_list, *item; // Decide variable type (list) if (!PyArg_ParseTuple(args, "O", &c_list)){ return NULL; } // Check list if PyList_Check(c_list){ // get length of the list n = PyList_Size(c_list); }else{ return NULL; } // Calculate list sum for (int i = 0; i < n; i++){ item = PyList_GetItem(c_list, i); a = PyLong_AsLong(item); // Increment the reference count sum = sum + a; Py_DECREF(item); // Decrement the reference count } Py_DECREF(c_list); // Decrement the reference count return Py_BuildValue("l", sum); // return Py_BuildValue("i", sum); } |
参照カウントが増減する箇所に以下のコメントを追記しました。
- Increment the reference count
- Decrement the reference count
↑のソースコードはPython C APIの”参照カウント”に関する【公式マニュアル】を参考に作成しました。
特に問題はないと思いますが、Python C APIを使う際は慎重に”参照カウント”を管理して頂ければと思います。
【公式マニュアル】
Sample4 -py_list_param–
最終的に完成した“py_list_param.c”のコードは以下です。
※setup.pyと動作確認用のpythonコードも↑にあるので【前編】を参考に動かしてみて下さい。
Python C API サンプル -py_list_list–
最後に『pythonからリストを受け取り、C/C++でリストの中身を2倍にしてして返す』サンプルについて説明します。
Sample名 | 引数 | 戻り値 |
py_hello | なし | なし |
py_add | int | int |
py_param_list | int | リスト |
py_list_param | リスト | int |
py_list_list | リスト | リスト |
今まで説明してきた内容の応用編になります。
get list and return list
【前編】含め、ここまで説明した内容を応用することで、以下のC/C++コードを作成できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include "../../../include/python3.6m/Python.h" // C function "get list and Multiply 2 and item of list" static PyObject* c_multiply_list(PyObject* c_list, int n) { long a; PyObject* result_list, *item; // make python list (length n) result_list = PyList_New(n); for (int i = 0; i < n; i++){ item = PyList_GetItem(c_list, i); a = PyLong_AsLong(item); // PyObject -> long a = 2 * a; item = Py_BuildValue("l", a); // long -> PyObject PyList_SET_ITEM(result_list, i, item); Py_DECREF(item); } Py_DECREF(c_list); return result_list; } // C function "get list and return list" static PyObject* py_list(PyObject* self, PyObject* args) { int n; PyObject* c_list; // decide type (list) if (!PyArg_ParseTuple(args, "O", &c_list)){ return NULL; } // Check list if PyList_Check(c_list){ // get length of the list n = PyList_Size(c_list); }else{ return NULL; } return c_multiply_list(c_list, n); } |
解説は以下の通りです。
- ピュアC/C++関数ではないが、”c_multiply_list”をラッピング
- 今まで解説した内容の組み合わせで実現
上記C/C++コードの”Python C API”に関するポイントは既に解説済みなので、いきなり最後のサンプル(Sample5)を公開
Sample5 -py_list_list–
最終的に完成した“py_list_list.c”のコードは以下です。
※setup.pyと動作確認用のpythonコードも↑にあるので【前編】を参考に動かしてみて下さい。
まとめ -後編-
今回、”Python C API”を使いたい人の手助けになれば良いと考え、私自身が勉強した内容をオープンにしました。
ただし、”Python C API”は奥が深いので網羅しきれなかった部分がありますし、私が公式マニュアルの内容を誤って解釈した部分もあるかも(?)しれません。
本記事に限らず、私が技術ブログ(特にプログラミング関係)を書くときは必ず調査を行います。そのときに気を付けているポイントが以下です。
【技術調査のポイント】
- 1つの記事・1冊の本に書いてある内容が全てではない
- 同じ処理を別のコードで実現できる
- 開発者が変われば思想が変わり、コードも変わる
- ある記事や本のコードが自身のベストコードとは限らない
- 情報は更新される
本記事も例外ではありません。私はベストを尽くしてブログを書いていますが、それでも間違えることはあるし、コードや文章などの好みも人それぞれです。
自身の情報リテラシーを信じて、本記事の必要な部分だけを抽出して頂ければ嬉しいです。
おまけ -Sample 6-
【前編】を読んだ人から以下の質問を頂きました。
【前編】『The Module’s Method Table and Initialization Function』の項目(目次からジャンプできます)で複数メソッドを定義できることを書いています。
また、本記事の内容は”入門レベル”なので…
と考えていますが、勤め先でメンター(後輩の技術指導)なども担当しているため、質問されるとヒントを与えたくなってしまう!笑
Sample4 -py_list_param–を改良し、複数メソッドにより、整数だけでなく浮動小数点にも対応できるサンプルを作成しました。
”実践”のヒントになると嬉しいです(*・ω・)ノ♪
(完)