Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
この記事は「深入りしないCython入門」の続きです。
今回もあまり深入りしないようにCythonに入門していこう。
なお、この記事はあくまで深入りせずに、楽してCythonのおいしい部分を頂くことを目的としている。
Cython記法早めぐり
Cythonの記法はそれほど難しくない、Cythonのチュートリアルに良いサンプルコードがあったので、それを拝借しよう。
defmyfunction(x,y=2):a=x-yreturna+x*ydef_helper(a):returna+1classA:def__init__(self,b=0):self.a=3self.b=bself._scale=2.0self.read_only=1.0deffoo(self,x):returnx+_helper(1.0)上のコードをCythonで最適化するとこうなる。
%%cythoncpdefintmyfunction(intx,inty=2):cdefinta=x-yreturna+x*ycdefdouble_helper(doublea):returna+1cdefclassA:cdefpublicintxcdefpublicintycdefdouble_scalecdefreadonlyfloatread_onlydef__init__(self,intb=0):self.a=3self.b=bself._scale=2.0self.read_only=1.0cpdefdoublefoo(self,doublex):return(x+_helper(1.0))*self._scale変数、引数、戻り値の型指定の方法は見ればわかるので、説明するまでもないだろう。
関数宣言
関数宣言をよく見ると、myfunction関数はcpdef、_helper関数はcdefで定義されている、関数宣言の一覧は以下の通り。
| 宣言 | 説明 |
|---|---|
| def | 低速、Pythonから呼び出す事ができる |
| cdef | 高速、Pythonからは呼び出しできない、Cython内のみ使用可 |
| cpdef | defとcdefのハイブリッド、Pythonから呼ばれる場合def、Cythonから呼ばれる場合はcdefで呼び出される |
cdefクラス
クラス宣言でcdef class Aとすると、cdefクラスとなる。
cdefクラスは、普通のクラスはdictでアトリビュートを管理しているのに比べ、構造体で管理しているため、メモリ効率もよくアクセスも高速であるが、以下のような制限を受ける。
- 動的なメソッド/メンバの追加不可
- cdefメソッドを親とした多重継承不可、単一継承は可
メンバの定義は以下のように事前に定義しなければならない。
cdefclassA:cdefpublicintxcdefpublicintycdefdouble_scalecdefreadonlyfloatread_only..._scaleメンバのようにpublicを付けないとPythonからの参照は不可である。
また、read_onlyメンバのようにreadonly属性をつけるとPythonからの変更が不可となる。
a=A()a._scale# エラーa.read_only=2.0# エラーCythonで使うファイル拡張子一覧
| 拡張子 | 説明 |
|---|---|
| .pyx | 実装ファイル、プログラム本体と考えればよい |
| .pxd | 定義ファイル |
| .pxi | インクルードファイル |
以上のことを知っていれば、だいたい困らないであろう。
pure Pythonモード
実際にPythonプログラムの高速化手順としては、オリジナルのプログラムに型定義を書き加えていくというのが一般的な手順である。
そこで、あえて違うアプローチを提案しよう、それが「pure Pythonモード」である。
上記のサンプルをpure Pythonモードで書き直してみよう。
%%cythonimportcython@cython.ccall@cython.locals(x=cython.int,y=cython.int)@cython.returns(cython.int)defmyfunction(x,y=2):a=x-yreturna+x*y@cython.cfunc@cython.locals(a=cython.double)@cython.returns(cython.double)def_helper(a):returna+1@cython.cclassclassA:a=cython.declare(cython.int,visibility='public')b=cython.declare(cython.int,visibility='public')_scale=cython.declare(cython.double)read_only=cython.declare(cython.double,visibility="readonly")@cython.locals(b=cython.int)def__init__(self,b=0):self.a=3self.b=bself._scale=2.0self.read_only=1.0@cython.ccall@cython.locals(x=cython.double)@cython.returns(cython.double)deffoo(self,x):returnx+_helper(1.0)*self._scalePythonコードにimport cythonして、ひたすらデコレートで型情報を追加していくスタイルである。これで、同一のファイルでPythonでの実行と、Cythonでのコンパイルを兼ねる事ができる。
関数外に型情報を定義するため、関数内のPythonコード部分は一切変更しなくて良い。
処理の部分の可読性はそのままなので、デコレータの嵐に見慣れれば意外と快適である。
Cythonのpure Pythonモードの詳細は公式ドキュメント見てほしい、簡潔なチュートリアルなので英文は殆ど無いので読みやすい。
補助(agumenting).pxdファイル
pure Pythonモードで関数内のコードをそのままに高速化ができるが、.pxdファイルを使うと.pyファイルをまるごと変更せずに高速化することができる。
この説明は公式マニュアルに簡潔な説明があるので、一部抜粋する。
.py ファイルと同名の .pxd が見つかると、 Cython は cdef されたクラスや、 cdef/cpdef され た関数やメソッドを走査します。次に、 .py 中の対応するクラス・ 関数・メソッドを、適切な型に変換します。 従って、もし下記のような a.pxd があったとして、:
cdefclassA:cpdeffoo(self,inti)同時に以下のような a.py というファイルがあると、:
classA:deffoo(self,i):print"Big"ifi>1000else"Small"コードは下記のように解釈されます:
cdefclassA:cpdeffoo(self,inti):print"Big"ifi>1000else"Small"タイプヒントとCythonの連携(希望的観測)
pure Pythonモードのコードをよく見るとPyCharmのタイプヒンティングに似ている。
私はPyCharmのタイプヒントをよく使うので、pure Pythonモードは使いやすく感じた。
classA:""" :type a: int :type b: int"""def__init__(self,b=0):""" :type b: int"""self.a=3self.b=bdeffoo(self,x):""" :type x: float :rtype: float"""returnx*float(self.a)また、Pythonには型情報のみを書いたスタブファイル(.pyi)というものがる。
スタブファイルは最後に説明した「補助(agumenting).pxdファイル」に非常によく似ている。
将来的にはタイプヒントを書いたPythonコード、または型アノテーションを書いたPythonコードは、コードに手をいれずとも自動でCythonによる高速化がされる(もちろん完全な高速化は難しいだろうが)ようになると個人的には嬉しい。
しかし、私の調べた限りではそのような情報は見つからなかった、タイプヒントとCython自動化について情報を持っている方は是非ご教授いただきたい。
まとめ
かなり早足だが、Cythonの機能を紹介した。
Cythonの機能はまだまだ沢山あるが、とりあえずCythonを使って高速化するには充分だろう。
更にCythonの概要を知りたいのであれば、下記のスライドがよくまとまっているのでオススメである。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
