web-dev-qa-db-ja.com

PythonでCライブラリをラップする:C、Cython、またはctypes?

PythonアプリケーションからCライブラリを呼び出したい。 API全体をラップするのではなく、私のケースに関連する関数とデータ型のみをラップします。ご覧のとおり、3つの選択肢があります。

  1. Cで実際の拡張モジュールを作成します。おそらくやりすぎです。また、拡張機能の記述を学習するオーバーヘッドを回避したいと思います。
  2. Cython を使用して、Cライブラリの関連部分をPythonに公開します。
  3. ctypes を使用して外部ライブラリと通信し、Pythonですべてを実行します。

2)または3)のどちらが良い選択かわかりません。 3)の利点は、ctypesが標準ライブラリの一部であり、結果のコードが純粋なPythonになることです。実際にその利点がどれほど大きいかはわかりません。

どちらの選択にも利点/欠点はありますか?どのアプローチをお勧めしますか?


編集:すべての回答に感謝します。同様のことをしたい人に良いリソースを提供します。もちろん、単一のケースについてはまだ決定が行われています。「これは正しいことです」というような答えはありません。私の場合は、おそらくctypesを使用しますが、他のプロジェクトでCythonを試してみるのも楽しみです。

真の答えは1つではないため、答えを受け入れるのはいくぶんarbitrary意的です。 FogleBirdの答えを選んだのは、ctypesについての洞察が得られるためであり、現在、最も投票数の多い答えです。ただし、すべての回答を読んで概要を把握することをお勧めします。

再度、感謝します。

265
balpha

ctypesは、それを迅速に実行するための最善の策であり、まだPythonを書いているので、一緒に作業するのは楽しいことです。

最近、ctypesを使用してUSBチップと通信するための FTDI ドライバーをラップしました。私はそれをすべて完了し、1営業日未満で作業しました。 (必要な機能のみを実装しました。約15個の機能です)。

以前は、同じ目的でサードパーティのモジュール PyUSB を使用していました。 PyUSBは、実際のC/Python拡張モジュールです。しかし、読み取り/書き込みのブロックを行っているときにPyUSBがGILをリリースしなかったため、問題が発生していました。そのため、ネイティブ関数を呼び出すときにGILを解放するctypesを使用して独自のモジュールを作成しました。

注意すべきことの1つは、ctypesが使用しているライブラリの#define定数と内容ではなく、関数のみを認識するため、独自のコードでこれらの定数を再定義する必要があることです。

コードがどのように見えたかの例を次に示します(多くの部分を切り取って、その要点を見せようとしているだけです):

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

誰かがさまざまなオプションについて ベンチマーク を行いました。

C++ライブラリを多くのクラス/テンプレート/などでラップしなければならなかった場合、私はもっとためらうかもしれません。ただし、ctypesは構造体でうまく機能し、Pythonに callback を使用することもできます。

109
FogleBird

Cythonはそれ自体が非常にクールなツールであり、学ぶ価値があり、驚くほどPython構文に近いものです。 Numpyで科学的な計算を行う場合、Cythonは高速な行列演算のためにNumpyと統合されるため、進むべき道です。

CythonはPython言語のスーパーセットです。有効なPythonファイルを投げると、有効なCプログラムが吐き出されます。この場合、CythonはPython呼び出しを基礎となるCPython APIにマップするだけです。これにより、コードが解釈されなくなるため、おそらく50%の高速化になります。

いくつかの最適化を得るには、Cythonに型宣言などのコードに関する追加の事実を伝える必要があります。十分に言えば、コードを純粋なCに煮詰めることができます。つまり、PythonのforループはCのforループになります。ここでは、大幅な速度の向上が見られます。ここから外部Cプログラムにリンクすることもできます。

Cythonコードの使用も非常に簡単です。私はマニュアルがそれを難し​​くしていると思った。あなたは文字通りただやる:

$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so

Pythonコードでimport mymoduleを実行すると、Cにコンパイルされることを完全に忘れることができます。

いずれにせよ、Cythonはセットアップと使用を開始するのが非常に簡単なので、Cythonがニーズに合っているかどうかを確認することをお勧めします。探しているツールではないことが判明しても無駄にはなりません。

97
carl

PythonアプリケーションからCライブラリを呼び出すには、 cffi もあります。これは、 ctypes。 FFIに新鮮な外観をもたらします。

  • ctypesとは対照的に)魅力的でクリーンな方法で問題を処理します
  • Python以外のコードを記述する必要はありません(SWIG、Cython、...など)
40
Robert Zaremba

私はそこに別のものを投げます: SWIG

学習は簡単で、多くのことを正しく行い、さらに多くの言語をサポートしているため、学習に費やした時間は非常に役立ちます。

SWIGを使用する場合、新しいpython拡張モジュールを作成しますが、SWIGを使用すると、ほとんどの面倒な作業を行うことができます。

21
Chris Arguin

個人的には、Cで拡張モジュールを作成します。Python C拡張にin怖されないでください。作成するのは難しくありません。ドキュメントは非常に明確で有用です。 PythonでC拡張機能を初めて作成したとき、それを作成する方法を理解するのに約1時間かかりました。

18
mipadi

ctypes は、処理するコンパイル済みライブラリblob(OSライブラリなど)を既に持っている場合に最適です。ただし、呼び出しのオーバーヘッドは厳しいため、ライブラリに多くの呼び出しを行い、とにかくCコードを作成する(または少なくともコンパイルする)場合は、 cython 。それほど作業は必要ありません。結果のpydファイルを使用する方がはるかに高速でPythonicになります。

私は個人的にpythonコードの高速化のためにcythonを使用する傾向があります(ループと整数の比較はcythonが特に優れている2つの領域です)。 Boost.Python に切り替えます。 Boost.Pythonはセットアップが難しい場合がありますが、一度動作させると、C/C++コードのラッピングが簡単になります。

cythonはラッピングにも優れています numpy (私は SciPy 2009の議事録 から学びました)が、numpyを使用したことがないので、コメントできません。

10
Ryan Ginstrom

定義済みのAPIを備えたライブラリを既にお持ちの場合、ctypesが最良のオプションだと思います。少し初期化するだけで、その後はおおまかにライブラリを呼び出すことができます。

CythonまたはCで拡張モジュールを作成すること(それほど難しくありません)は、新しいコードが必要な場合に便利です。そのライブラリを呼び出して、複雑で時間のかかるタスクを実行し、結果をPythonに渡します。

単純なプログラムの別のアプローチは、異なるプロセスを直接実行し(外部でコンパイル)、結果を標準出力に出力し、サブプロセスモジュールで呼び出します。時にはそれが最も簡単なアプローチです。

たとえば、そのように多かれ少なかれ動作するコンソールCプログラムを作成する場合

$miCcode 10
Result: 12345678

Pythonから呼び出すことができます

>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], Shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

少し文字列の書式設定を行うと、任意の方法で結果を取得できます。標準エラー出力をキャプチャすることもできるため、非常に柔軟です。

9
Khelben

Cythonではなくctypesを使用し、他の回答には記載されていない問題が1つあります。

Ctypesを使用すると、結果は使用しているコンパイラにまったく依存しません。ネイティブ共有ライブラリにコンパイルされる可能性のある任意の言語を使用して、ライブラリを作成できます。どのシステム、どの言語、どのコンパイラーであるかは重要ではありません。ただし、Cythonはインフラストラクチャによって制限されています。たとえば、WindowsでIntelコンパイラを使用する場合、cythonを機能させるのははるかに難しいです:コンパイラをcythonに「説明」し、この正確なコンパイラで何かを再コンパイルする必要があります。これにより、移植性が大幅に制限されます。

6
Misha

GLib を使用しているライブラリに GObject Introspection を使用する可能性も1つあります。

2
plaes