PythonでのCライブラリのラッピング:C、Cythonまたはctypes?


284

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

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

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

どちらを選択しても、長所/短所はありますか?どちらのアプローチをお勧めしますか?


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

正解は1つではないため、1つを受け入れることはいくぶん恣意的です。私はFogleBirdの回答を選択しました。これはctypesに対するいくつかの優れた洞察を提供し、現在、最も投票された回答でもあるからです。ただし、概要を理解するためにすべての回答を読むことをお勧めします。

再度、感謝します。


3
ある程度、関連する特定のアプリケーション(ライブラリが行うこと)がアプローチの選択に影響を与える可能性があります。ctypesを使用して、ハードウェアのさまざまな部分(オシロスコープなど)のベンダー提供のDLLとの通信に成功しましたが、CythonまたはSWIGと比べて余分なオーバーヘッドがあるため、数値処理ライブラリと通信するために必ずしも最初にctypeを選択するわけではありません。
Peter Hansen、

1
今、あなたはあなたが探していたものを持っています。4つの異なる答え(誰かがSWIGも見つけた)。意味では、今、あなたは4つの選択肢の代わりに、3持っていること
ルカRahneを

@raluそれも私が考えていることです:-)しかし、真剣に、私はpro / conテーブルまたは「ここにあなたがする必要があること」と言う単一の答えを期待していませんでした(または望んでいませんでした)。意思決定についての質問には、理由を挙げて、可能な選択肢それぞれの「ファン」が最も適切に回答します。コミュニティの投票は、私自身の仕事と同様に、その役割を果たします(議論を調べ、それを私の訴訟に適用し、提供されたソースを読むなど)。要するに、ここにいくつかの良い答えがあります。
balpha 2009

では、どのアプローチを採用するのですか?:)
FogleBird 2009

1
私の知る限り(間違っている場合は訂正してください)、CythonはPyrexのフォークであり、開発が進んでいるため、Pyrexはかなり時代遅れになっています。
balpha 2009

回答:


115

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にコールバックすることさえできます


5
ctypesの賞賛に加わりますが、1つの(文書化されていない)問題に気づきます:ctypesはフォークをサポートしていません。ctypesを使用するプロセスからフォークし、親プロセスと子プロセスの両方がctypesを使用し続けると、共有メモリを使用するctypesに関係する厄介なバグに遭遇します。
Oren Shemesh 2012

1
@OrenShemeshこの問題について、私に指摘できるその他の参考資料はありますか?親プロセスだけがctypes(のpyinotify)を使用していると思うので、現在取り組んでいるプロジェクトで安全かもしれませんが、問題をより完全に理解したいと思います。
zigg

この一節は私を大いに助けてくれます。One thing to note is that ctypes won't know about #define constants and stuff in the library you're using, only the functions, so you'll have to redefine those constants in your own code.だから、そこにある定数を定義しなければなりませんwinioctl.h....
swdev 2014

パフォーマンスはどうですか?ctypesボトルネックがPythonからCへのインターフェースであるため、c-extensionよりもはるかに遅い
TomSawyer

154

警告:Cythonコア開発者の見解。

ほとんどの場合、ctypesよりCythonをお勧めします。その理由は、アップグレードパスがはるかにスムーズになるためです。ctypesを使用する場合、最初は多くのことは簡単です。コンパイルやビルドの依存関係などを行わずに、プレーンなPythonでFFIコードを書くのは確かにクールです。ただし、ある時点で、ループまたは相互に依存する一連の長い呼び出しのいずれかで、Cライブラリを頻繁に呼び出す必要があり、その速度を上げたい場合があります。これが、ctypesではそれができないことに気付くところです。または、コールバック関数が必要で、Pythonコールバックコードがボトルネックになっている場合は、それを高速化したり、Cに移動したりすることもできます。繰り返しますが、ctypesではそれはできません。

Cython、OTOHを使用すると、ラッピングコードと呼び出しコードを自由に自由に細くしたり厚くしたりできます。通常のPythonコードからCコードへの単純な呼び出しから始めることができます。Cythonは、追加の呼び出しオーバーヘッドなしで、Pythonパラメーターの変換オーバーヘッドが非常に少ないネイティブC呼び出しに変換します。Cライブラリに対して高額な呼び出しを行いすぎているある時点でさらに高いパフォーマンスが必要であることに気付いた場合は、周囲のPythonコードに静的型で注釈を付け始め、CythonがそれをCに直接最適化できるようにします。または、CythonでCコードの一部を書き直して、呼び出しを回避し、ループをアルゴリズムによって特殊化および強化することもできます。高速なコールバックが必要な場合は、適切なシグネチャを使用して関数を記述し、Cコールバックレジストリに直接渡すだけです。繰り返しますが、オーバーヘッドはなく、Cの呼び出しパフォーマンスがわかります。また、Cythonで十分な速度でコードを取得できない可能性が非常に低い場合でも、C(またはC ++またはFortran)で本当に重要な部分を書き直して、Cythonコードから自然にネイティブに呼び出すことを検討できます。ただし、これが唯一の選択肢ではなく、最後の手段になります。

したがって、ctypesは単純なことを実行し、何かをすばやく実行するのに適しています。ただし、物事が成長し始めるとすぐに、最初からCythonを使用したほうがよいことに気付くようになります。


4
+1それらは良い点です、本当にありがとう!ボトルネックのパーツだけをCythonに移動することは、本当にこれだけのオーバーヘッドになるのではないかと思います。ただし、何らかのパフォーマンスの問題が予想される場合は、最初からCythonを使用することもできます。
balpha

これは、CとPythonの両方の経験を持つプログラマーにも当てはまりますか その場合、Cループのベクトル化(SIMD)の方が簡単な場合があるため、Python / ctypesの方が適していると主張するかもしれません。しかし、それ以外には、Cythonの欠点は考えられません。
Alex van Houten 2012年

答えてくれてありがとう!Cythonに関して問題があったのは、ビルドプロセスを正しく行うことです(ただし、これはPythonモジュールを以前に作成したことがないことにも関係しています)。以前にコンパイルするか、Cythonソースファイルをsdistや同様の質問に含める必要があります。:私は誰もが同じような問題/疑問がある場合には、それについてのブログ記事を書いたmartinsosic.com/development/2016/02/08/...
Martinsos

答えてくれてありがとう!Cythonを使用する際の1つの欠点は、演算子のオーバーロードが完全に実装されていないことです(例:)__radd__。これは、クラスが組み込み型(intおよびなどfloat)と対話することを計画している場合に特に煩わしいものです。また、cythonのマジックメソッドは、一般的に少しバグがあります。
Monolith

100

Cythonはそれ自体がかなりクールなツールであり、学ぶ価値があります。驚くほどPython構文に近いものです。Numpyを使用して科学計算を行う場合、高速な行列演算のためにNumpyと統合されているCythonが適しています。

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

そして、あなたはimport mymoduleあなたのPythonコードでそれがCにコンパイルされることを完全に忘れることができます。

いずれにせよ、Cythonはセットアップが簡単で使い始めることができるので、自分のニーズに合うかどうか試してみることをお勧めします。それがあなたが探しているツールではないことが判明しても、それは無駄ではありません。


1
問題ない。Cythonの良いところは、必要なものだけを学べることです。少しだけ改善したい場合は、Pythonファイルをコンパイルするだけで完了です。
カール、

18
「そこに有効なPythonファイルを投げれば、有効なCプログラムが吐き出されます。」<- 残念ながら、いくつかの制限があります 。
ランディシリング

7
リリースごとに問題が少なくなり、そのページに「ほとんどの問題は0.15で解決された」と記載されています。
Henry Gomersall、2012年

3
追加すると、cythonコードをインポートする簡単な方法があります。cythonコードをmymod.pyxモジュールとして記述してから実行するimport pyximport; pyximport.install(); import mymodと、コンパイルはバックグラウンドで行われます。
Kaushik Ghose 2014

3
@kaushikさらに単純なのはpypi.python.org/pypi/runcythonです。だけを使用してくださいruncython mymodule.pyx。そして、pyximportとは異なり、より厳しいリンクタスクに使用できます。唯一の注意点は、私がそのために20行のbashを書いたのが偏っていることです。
RussellStewart 2015

42

PythonアプリケーションからCライブラリを呼び出すために、ctypesの新しい代替手段であるcffiもあります。FFIのデザインが一新されました。

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

OPが望んでいたように、ラッピングに行く方法は間違いありません。cythonは、ホットループを自分で書くのに最適ですが、インターフェイスの場合、cffiは単にctypesから直接アップグレードしたものです。
ヒツジの飛行2015年

21

私はもう1つ投げます:SWIG

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

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


18

個人的には、Cで拡張モジュールを作成します。PythonCの拡張機能に威圧されないでください。作成するのはそれほど難しくありません。ドキュメントは非常に明確で役に立ちます。最初にPythonでC拡張機能を作成したとき、その作成方法を理解するのに約1時間かかったと思います。


Cライブラリのラッピング。実際にコードはここにあります:github.com/mdippery/lehmer
mipadi

1
@forivall:コードは実際にはそれほど役に立ちませんでした。また、そこにはより優れた乱数ジェネレータがあります。コンピューターにバックアップしかない。
mipadi

2
同意した。PythonのC-APIは、見た目ほど怖くない(Cを知っていると仮定)。ただし、Pythonやライブラリ、リソース、開発者のリザーバーとは異なり、Cで拡張機能を作成するときは、基本的に自分で行います。おそらくその唯一の欠点です(通常、Cでの書き込みに伴うもの以外)。
Noobサイボット2014年

1
@mipadi:まあ、でもPython 2.xと3.xでは違いがあるので、Cythonを使用して拡張機能を記述し、Cythonにすべての詳細を理解させてから、生成されたCコードをPython 2.x用にコンパイルするか、必要に応じて3.x。
0xC0000022L 2016

2
@mipadi githubリンクが停止しているようで、archive.orgで利用できないようです。バックアップはありますか?
jrh

11

ctypesは、コンパイル済みのライブラリBLOB(OSライブラリなど)をすでに処理している場合に最適です。ただし、呼び出しのオーバーヘッドは非常に大きいので、ライブラリに対して多くの呼び出しを行い、とにかくCコードを作成する(または少なくともコンパイルする)場合は、シトン。作業はそれほど多くなく、結果のpydファイルを使用する方がはるかに高速でPython的になります。

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

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


11

すでに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

少し文字列をフォーマットすることで、結果を好きなように取得できます。また、標準エラー出力をキャプチャできるため、非常に柔軟です。


この答えには誤りはありませshell=Trueんが、ユーザーが実際にシェルを取得したときにサブプロセスを呼び出すと、ある種のエクスプロイトが簡単に発生する可能性があるため、コードを他のユーザーがアクセスできるようにする場合は注意が必要です。開発者が唯一のユーザーである場合は問題ありませんが、世界には、このような何かを待っているだけで迷惑な人がたくさんいます。
ベン

7

私がcythonではなくctypesを使用し、他の回答では言及されていない問題が1つあります。

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


4

Windowsを対象としていて、独自のC ++ライブラリをラップすることを選択した場合、msvcrt***.dll(Visual C ++ランタイム)の異なるバージョンがわずかに互換性がないことにすぐに気付くでしょう。

これはCython、結果wrapper.pydmsvcr90.dll (Python 2.7)またはmsvcr100.dll (Python 3.x)にリンクされているため、使用できない場合があることを意味します。ラップしているライブラリが異なるバージョンのランタイムに対してリンクされている場合は、運が悪いです。

次に、物事を機能させるには、C ++ライブラリ用のCラッパーを作成する必要があります。そのラッパーDLLをC ++ライブラリと同じバージョンのに対してリンクしますmsvcrt***.dll。そして、を使用ctypesして、ランタイムに動的に手動のラッパーDLLをロードします。

したがって、次の記事で詳細に説明されている小さな詳細がたくさんあります。

「Beautiful Native Libraries (in Python)」:http : //lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/


この記事は、Microsoftコンパイラの互換性で発生する問題とは何の関係もありません。WindowsでCython拡張機能を動作させることは、それほど難しくありません。MinGWをほとんどすべてに使用できました。良いPythonディストリビューションは助けになります。
IanH 2014年

2
+1は、Windowsで発生する可能性のある問題について言及しています(現在、私も同様です...)。@IanHそれは一般的にウィンドウについてはそれほどではありませんが、Pythonディストリビューションと一致しない特定のサードパーティのlibで立ち往生している場合は混乱します。
セバスチャン2014


2

私はこれが古い質問であることを知っていますが、のようなものを検索すると、このことがGoogleに表示されctypes vs cythonます。ここでの回答のほとんどは、すでに習熟している人、cythonまたはcそれらを学ぶために投資する必要がある実際の時間を反映していない人によって書かれていますソリューションを実装します。私は両方の完全な初心者です。私はこれまで触れたことはなくcython、での経験はほとんどありませんc/c++

過去2日間、コードのパフォーマンスの重い部分をpythonよりも低いレベルに委任する方法を探していました。私は、両方の私のコードを実装ctypesしてCython、基本的に2つの単純な関数の構成されています。

処理する必要のある巨大な文字列リストがありました。通知liststringcPythonの文字列はデフォルトではUnicodeであり、c文字列はそうではないため、どちらの型もの型に完全には対応していません。Pythonのリストは、cの配列ではありません。

これが私の評決です。を使用しcythonます。それはpythonにより滑らかに統合され、一般的に扱いやすくなります。何かがうまくいかctypesない場合、segfaultがスローされるだけで、少なくともcython可能な場合は常にスタックトレースを使用してコンパイル警告が表示され、有効なPythonオブジェクトをで簡単に返すことができますcython

これは、同じ機能を実装するために両方に投資するのに必要な時間についての詳細な説明です。ちなみに私はC / C ++プログラミングをほとんどしませんでした:

  • Ctypes:

    • ユニコード文字列のリストをAC互換タイプに変換する方法の研究について約2時間。
    • C関数から文字列を適切に返す方法について約1時間。ここでは、関数を記述した後、実際にSOに独自のソリューションを提供しました。
    • 約30分でコードをcで記述し、動的ライブラリにコンパイルします。
    • Pythonでテストコードを記述して、cコードが機能するかどうかを確認するための10分。
    • 約1時間のテストとcコードの再配置。
    • 次に、cコードを実際のコードベースにプラグインしましたctypesmultiprocessing、そのハンドラーはデフォルトでは選択できないため、モジュールではうまく機能しません。
    • 約20分multiprocessing、モジュールを使用しないようにコードを再配置して、再試行しました。
    • その後、私のcコードの2番目の関数は、コードベースにsegfaultを生成しましたが、テストコードに合格しました。まあ、これはおそらくエッジケースでうまくチェックしないことの私のせいです、私は簡単な解決策を探していました。
    • 約40分間、これらのセグメンテーション違反の考えられる原因を特定しようとしました。
    • 関数を2つのライブラリーに分割して、再試行しました。私の2番目の機能にはまだセグメンテーション違反がありました。
    • 私は2番目の関数を手放し、cコードの最初の関数のみを使用することを決定し、それを使用するPythonループの2番目または3番目の反復で、UnicodeErrorすべてをエンコードおよびデコードしましたが、一部の位置でバイトをデコードしませんでした明示的に。

この時点で、私は代替案を探すことに決め、調査することに決めましたcython

  • Cython
    • cython hello worldを 10分間読みます
    • 代わりにでcythonを使用する方法についてSOを確認する15分。setuptoolsdistutils
    • cython型とpython型についての10分の読書。私は静的型付けにほとんどの組み込みPython型を使用できることを学びました。
    • 15分で、PythonコードにCythonタイプを再アノテーションします。
    • setup.pyコードベースでコンパイル済みモジュールを使用するように変更する10分。
    • モジュールmultiprocessingをコードベースのバージョンに直接接続しました。できます。

記録のために、私はもちろん、私の投資の正確なタイミングを測定しませんでした。私がctypesを扱っている間に必要な精神的な努力が原因で、私の時間の認識が少し注意深いものだったのは、よくあることです。しかし、それは扱うの感触を伝える必要がありますcythonし、ctypes

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.