PythonのC言語拡張モジュール作成ガイド

Version: 0.2
Last Update: 2013 年 04 月 20 日

Python API

Python APIはPythonインストール標準のC言語APIを使用し、拡張モジュールを作成します。

使用ファイル

PythonAPIを使用したCソースを準備します。

#include <Python.h>

static PyObject *
fact(PyObject *self, PyObject *args)
{
    int n;
    int i;
    int ret=1;

    if (!PyArg_ParseTuple(args, "i", &n))
        return NULL;

    for (i=n; i>0; i--) ret *= i;

    return Py_BuildValue("i", ret);
}

static PyObject *
hello(PyObject *self)
{
    printf("Hello World!!\n");
    Py_RETURN_NONE;
}

static char ext_doc[] = "C extention module example\n";

static PyMethodDef methods[] = {
    {"hello", (PyCFunction)hello, METH_NOARGS, "print hello world.\n"},
    {"fact", fact, METH_VARARGS, "return factorial.\n"},
    {NULL, NULL, 0, NULL}
};

void initext(void)
{
    Py_InitModule3("ext", methods, ext_doc);
}

作成方法

$ gcc -Wall -fPIC -c ext.c -I/usr/include/python2.6
$ gcc -shared -o ext.so ext.o
$ ls
ext.c  ext.o  ext.so
>>> import ext
>>> ext.hello()
Hello World!!
>>> ext.fact(5)
120
>>> ext.fact(10)
3628800

ctypes

PythonAPIに最適化されていない生のCソースで作成した共有ライブラリ内の関数を モジュールアクセスで呼び出すことができます。 Python2.5からは標準ライブラリとして使用することができます。

使用ファイル

以下のようなCソースを準備するだけです。

#include <stdio.h>

void hello(void)
{
    printf("Hello World!!\n");
}

int fact(int n)
{
    int i;
    int ret=1;

    for (i=n; i>0; i--) ret *= i;

    return ret;
}

作成方法

GCCを使っていつもどおりの共有ライブラリを作成します。

gcc -Wall -fPIC -c ext.c -I/usr/include/python2.6
gcc -shared -o ext.so ext.o

使用方法

ctypes を使ってモジュールにアクセスしてみます。

>>> import ctypes
>>> ext = ctypes.CDLL("./ext.so")
>>> ext.hello()
Hello World!!
>>> ext.fact(5)
120
>>> ext.fact(10)
3628800

SWIG

プロジェクトページ : SWIG

別途インターフェースファイル(*.i)を用意することで、 Cのスタイルを保ったコードで拡張モジュールを記述できます。 使ってみた感想としては、ラッピング関数を定義したファイルが作成され ファイル構成がやや煩雑になりますが、PythonAPIをいちいち調べたりしなくてもよいので、 より規模の大きなモジュールを作成する際などは、生産性の面で有利かなと感じました。 SWIGがインストールされている必要があります。

使用ファイル

Cソース( ext.c )は以下になります。

#include <stdio.h>

void hello(void)
{
    printf("Hello World!!\n");
}

int fact(int n)
{
    int i;
    int ret=1;

    for (i=n; i>0; i--) ret *= i;

    return ret;
}

インターフェースファイル( ext.i )は以下のような内容になります。

%module ext
%{
extern int fact(int n);
extern void hello(void);
%}
extern int fact(int n);
extern void hello(void);

作成方法

swig コマンドで後ほど作成する共有ライブラリ( _ext.so )にアクセスする Pythonのラッパースクリプト( ext.py )と ext.cをラッピングするCソース( ext_wrap.c)が作成されます。

$ swig -python ext.i
$ ls
ext.py ext_wrap.c
$ gcc -Wall -fPIC -c ext.c ext_wrap.c -I/usr/include/python2.6
$ gcc -shared -o _ext.so ext.o ext_wrap.o

Pyrex

プロジェクトページ : Pyrex

Python拡張モジュール作成用のPythonライクな構文を持つ言語と、 それをC言語に変換するツールセット(pyrexc)を含む処理系です。 個人的には若干構文が気に入らない感じです。Pyrex構文を覚えないとだめなのが。。。

def hello():
    print "Hello World!!"

def fact(int n):
    cdef int i
    cdef int ret

    ret = 1
    i = n
    while i > 0:
        ret *= i
        i -= 1
    return ret
$ pyrexc ext.pyx
$ gcc -Wall -fPIC -c ext.c -I/usr/include/python2.6
$ gcc -shared -o ext.so ext.o

pyrexc での変換がどこまで対応できるのかが気になります。

Cython

プロジェクトページ : Cython

Pyrexの派生であり、Pythonと上位互換があります。 よってPyrexよりもよりPythonらしく記述できます。

def hello():
    print "Hello World!!"

def fact(n):
    ret = 1
    i = n
    while i > 0:
        ret *= i
        i -= 1
    return ret
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("ext", ["ext.pyx"])]

setup(
    name = 'C extention module example',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules
)
$ python setup.py build_ext --inplace
$ ls
build ext.c ext.so

Pyrexと同じく大規模なモジュールでうまく作成できるかどうかが 気になるところです。

CFFI

まず、ある言語から別の言語で作成されたライブラリを呼び出す仕組みとして FFI(Foreign Function Interface)があります。

CFFIはFFIの仕組みを利用して、PythonからCで作成されたライブラリを呼び出す仕組みです。

共有ライブラリにアクセスする

Cで作成した共有ライブラリ( ext.so )を cffi.FFI.dlopen 経由で使用するPythonスクリプトは以下です。

from cffi import FFI

ffi = FFI()
ffi.cdef("""
void hello(void);
int fact(int n);
""")

lib = ffi.dlopen("./ext.so")
lib.hello()
print lib.fact(5), lib.fact(10)

直接Cのコードを記述する

cffi.FFI.verify を使用することでCのコードを直接記述し、呼び出すことができます。 以下がその例です。

from cffi import FFI

ffi = FFI()
ffi.cdef("""
void hello(void);
int fact(int n);
""")

_C = r"""
#include <stdio.h>

void hello(void)
{
    printf("Hello World!!\n");
}

int fact(int n)
{
    int i;
    int ret=1;
    for (i=n; i>0; i--) ret *= i;

    return ret;
}
"""
lib = ffi.verify(_C)
lib.hello()
print lib.fact(5), lib.fact(10)

変数や構造体を使う

cffi.FFI.cdef に直接構造体(struct)定義を記述し、 cffi.FFI.new 経由で変数を使用してみます。

from cffi import FFI

ffi = FFI()
ffi.cdef(r"""
typedef struct {
    int age;
    double weight;
    double height;
} Person;
void use_struct(Person *p);
void use_char(char *name);
""")

_C = r"""
#include <stdio.h>

typedef struct {
    int age;
    double weight;
    double height;
} Person;

void use_struct(Person *p)
{
    printf("age   : %d\n", p->age);
    printf("weight: %.2lf\n", p->weight);
    printf("height: %.2lf\n", p->height);
}

void use_char(char *name)
{
    printf("Full Name: %s\n", name);
}
"""
lib = ffi.verify(_C)

mike = ffi.new("Person[]", [(25, 181.2, 90.5)])
lib.use_struct(mike)

susan_fullname = "Suzan Zizi"
lib.use_char(susan_fullname)

目次

このページ