Python3 Advent Calendar - Pythonで2/3両方で動くコードを書く(C/API)
これは Python3 Advent Calendarの記事です。
こんにちわ、高校生です。
先日、子供(遼くん)が無事生まれましたが、毎日お世話で忙しいです。
Python3 Advent Calendar ということでPython3に関して書いてみたいと思います。
Python3対応というのはまあいろいろあるんですが、Pure Pythonで両方動くコード書こうとなると文法とかいろいろ面倒ですね。
ですがC/APIで書くとマクロで大きく処理を2系、3系と切り替えができるのでので容易に両バージョンをサポートするコードが書けます。
ということで2/3両方をサポートするコードをC/APIを中心に幾つか書いてみたいと思います。
(3系といっても3.2以降と思ってください)
Python3の判別
Python3であるかどうかは元々用意されているPythonのバージョンを確認できるマクロで容易に判断できます。
例:Python3系
#if PY_MAJOR_VERSION >= 3 #define PY3 #endif
単純に3系の場合にはこんな感じですね。
PythonのC/APIをCで利用する際のAPIが3.1以降変わったため、それらを考慮するならばこのような書き方になります。
例:Python3.1以降
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) || PY_MAJOR_VERSION > 3 #define PY3 #endif
PY_MINOR_VERSIONの他にも、HEXバージョンで判断する方法もあります。
(多分HEXが一番楽かも)
毎回、長いマクロ名を書くのがだるいので共通ヘッダなどに短めに再定義してしまうと楽だと思います。
モジュールの定義
モジュールの作成方法も2/3で変わっています。
ここでは先程の判定マクロを使用して2と3で分岐しています。
例:書き方の例
static PyMethodDef CoreMethods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; #ifdef PY3 #define INITERROR return NULL static struct PyModuleDef core_module_def = { PyModuleDef_HEAD_INIT, MODULE_NAME, NULL, -1, CoreMethods, }; PyObject * PyInit_core(void) #else #define INITERROR return PyMODINIT_FUNC initcore(void) #endif { PyObject *m; #ifdef PY3 m = PyModule_Create(&core_module_def); #else m = Py_InitModule3(MODULE_NAME, CoreMethods, ""); #endif if(m == NULL){ INITERROR; } ... #ifdef PY3 return m; #endif }
大きく異なるのはPy_InitModule3などがなくなった点です。
モジュール定義用の構造体にモジュール情報をセットしてPyModule_Createでモジュールを作成します。
またPython3以降は作成したモジュールオブジェクト返さないといけません。
(返り値が異なる。初期化メソッド名なども変更)
その他、モジュールの関数の定義などは特に変更はないです。
PyInt系関数の廃止
Python3からはPyInt_から始まる関数は排除されています。
Python3からは全てPyLongに統一する必要があります。
特に問題なければ2/3でもPyLongの関数を使ってしまってもいいかも知れませんね。
生成だけではばくチェックなどもPyLongの関数に変更になります。
#ifdef PY3 if(!PyLong_Check(sec)){ #else if(!PyInt_Check(sec)){ #endif
文字列の生成
色々なところで触れられていますが、Python3では通常文字列は全てUnicode型になります。
今までのstr型と同じような動作にしたい場合にはPyBytes系(PyString)の関数を使用することになります。
char *tmp; ... #ifdef PY3 return PyUnicode_FromString(tmp); #else //あるいはreturn PyString_FromString(tmp); return PyBytes_FromString(tmp); #endif
Python2.6以降はPyBytes系の関数が既に存在しているため、そちらを使う事を推奨します。
PyUnicode_FromStringは渡された文字列がUTF-8 encodedなものとして処理をします。多くの場合はこれで事足りると思います。
BytesとUnicode
Python3でも全てUnicodeを使えばいいのか?というとそうではありません。
生データを必要とする場合には、適時PyBytes_FromStringでBytesを返す必要があります。
例:
- ファイルからのデータの読込結果
- ソケットから読み込んだデータ
興味があればPEP-3333などを読むと使い分けに対する理解が深まるかも知れません。
char*型の参照
逆にPythonの文字列型をCで参照する場合にもUnicodeのAPIを使います。
char *ch; char *tmp; .... #ifdef PY3 PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(ip); ch = PyBytes_AsString(latin_item); Py_DECREF(latin_item); #else ch = PyBytes_AsString(ip); #endif
上記の例はlatin1のUnicodeである前提で処理しています。
(ネットワーク系の処理ではこれで十分だったりします。)
必要に応じてPyUnicode_Decode関数でデコードし、参照する必要があります。
上記ではUnicodeかどうかのチェックをしていませんが、必要に応じてPyUnicode_Checkを呼び出して下さい。
C API の提供、およびImport
あまり使われてないようですがC/APIで書かれたモジュールは他のCモジュールでも使用できるAPIを提供することができます。
2.7および3.2以降からは以前のPyCObject_FromVoidPtrではなくPyCapsule_系のAPIを使用します。
例:提供する側
#if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) #define USE_PYCAPSULE #endif ... PyObject *c_api_object; static void *_C_API[8]; .... #ifdef USE_PYCAPSULE c_api_object = PyCapsule_New((void *) _C_API, "xxxxx._C_API", NULL); #else c_api_object = PyCObject_FromVoidPtr((void *) _C_API, NULL); #endif if (c_api_object != NULL) { PyModule_AddObject(m, "_C_API", c_api_object); }
例:使用する側
#ifdef USE_PYCAPSULE #define C_API_Import() \ { \ _C_API = (void**)PyCapsule_Import("xxxxx._C_API", 0); \ } #else #define C_API_Import() \ { \ PyObject *module = PyImport_ImportModule("xxxxx"); \ if (module != NULL) { \ PyObject *c_api_object = PyObject_GetAttrString( \ module, "_C_API"); \ if (c_api_object != NULL && PyCObject_Check(c_api_object)) { \ _C_API = \ (void **) PyCObject_AsVoidPtr(c_api_object); \ Py_DECREF(c_api_object); \ } \ Py_DECREF(module); \ } \ } #endif
Pure PythonでPython3を判別する
Python側でも簡単に判別することができます。
マクロ同様、短めの関数名で定義しておくと使うとき楽だと思います。
import sys def is_py3(): return sys.hexversion >= 0x3000000
モジュール定義時に大きく分岐させることで両方で動くモジュールを作成することが出来ます。
例:
if is_py3(): #python3系の処理を書く class socket(_socket.socket): .... else: class socket(_socket.socket): ....
とざっくり両バージョンで動かすためのコードを紹介しました。
両方をサポートするコードを書いた場合にはtoxなどでテストをすることをオススメします。
(toxについてはそのうち誰かが書くと思います)
次はid:doloopwhileさんです。