cythonのコードを外部からimportする方法

この記事では,cythonを使ってpythonのコードをcompileしたものを外部から参照する方法を解説する.個人的な目的としては,jupyter notebook上からよく使うcythonでコンパイルしたコードを参照して,さらにjupyter notebook上で %cython 内で追加の整形を加えたい.cythonicに書いたコードをcompileするだけではないので注意が必要だ.

僕の使用環境は,macOS Mojave, Cythonのversionが0.29.14である.
コードは, git clone https://github.com/akitoshiblog/blogsite でcython_import下にある.

下準備

jupyter notebook上の操作の際には始めに %load_ext Cython を必ず回すようにする.

また,ファイルを操作した後にjupyter notebookをいじる際はカーネルを再起動し,%load_ext Cython を再び回す必要がある.%load_ext autoreload 系では対応できないので注意すること.

拡張子”.pxd” について

cythonには3つのファイル形式がある[1].”.py” or “.pyx” はコードを実装するファイル.”.pxd” は変数,関数,クラスの型を宣言するファイル.最後に”.pxi”はどのファイルを含めるか指定するファイルだ.

“.pxd”は変数,関数,クラスの型を宣言するファイルであり,”.py”や”.pyx”をコンパイルする際に,同名の”.pxd”ファイルがあると自動的にコンパイルされるファイルに組み込まれるようになっている.外部からファイルをcimportするためにはこの”.pxd”のファイルが必要となる.

以下のコードでは,変数の型を宣言した部分と,cのライブラリーであるmath.hからの関数の型を宣言した部分がある.

example.pxd

cdef enum otherstuff:
    sausage, eggs, lettuce

cdef struct spamdish:
    int oz_of_spam
    otherstuff filler
    
cdef extern from "math.h":
    double sqrt(double x)
    double sin(double x)
    double exp(double x)
    double log(double x)

このファイルはcompileする必要がなく,直接jupyter notebook上から参照が可能だ.

jupyter notebook上

%%cython -a

cimport example
from example cimport spamdish

cdef void prepare(spamdish *d):
    d.oz_of_spam = 42
    d.filler = example.sausage

def serve():
    cdef spamdish d
    prepare(&d)
    print(f'{d.oz_of_spam} oz spam, filler no. {d.filler}')
    
cpdef sin(double x):
    cdef float v
    v = example.sin(x)
    
    return(v)

example.pxdで指定した型が使われていること,また,example.sin()の部分がc言語でcompileされていることが確認できる.
このように .pxd ファイルは型の宣言に特化している.このファイルの中で関数を定義することも出来る[2]

外部からcimportできるファイル作り

“.pxd”のファイル構成が分かったので,次はコードの宣言と処理部分を分ける書き方について紹介する.次の3つのコードを用意する必要がある[3,4,5]

A.pyx, ここでcythonicな書き方をする.

cdef double plus(double x, double y):
    return(x + y)

cpdef double minus(double x,double y):
    return(x- y)

cdef class Aclass:
    def __cinit__(self):
        self.a = 3
    cpdef double add(self,double x = 3):
        return(self.a + x) 

    cpdef double minus(self):
        cdef int y 
        y = 2
        return(self.a - y )

    def multiple(self,x):
        return(self.a*x)

A.pxd, 関数とクラス,アトリビョートについてのみ型の宣言をする.クラス”Aclass”のメソッド”minus”内で宣言されているyについては書いてないことに注意する.

# cython: language_level=2
cdef double plus(double ,double )
cpdef double minus(double , double )

cdef class Aclass:
    cdef int a 
    cpdef double add(self,double x =*)
    cpdef double minus(self)

setup1.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

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

setup( name = "Myproject", 
        ext_modules =  cythonize(ext_modules,annotate=True)
)

cythonizeに annotate=True を引き渡すとjupyter notebook上でオプションに-a/–annotate を渡した時のように,compileがどこが上手くいっているかを示した.htmlファイルが生成される[6]
compileする際は,Linux系ならば,python setup1.py build_ext --inplace によって実行する.今回の場合は,”A.html”が作成された.

このようにすることで,jupyter notebook上でcythonをさらに書く時にコードを外部参照できるようになる.クラスの参照も行える.

jupyter notebook上

%%cython -a
cimport A

cpdef double plus():
    cdef float v
    v = A.plus(1,2)
    return(v)

cdef class Bclass(A.Aclass):
    cpdef double val_return(self):
        return(self.a)

以下のコードは全て正しく動作する.

jupyter notebook上

import A
print(plus())
print(A.minus(4,2))
a = A.Aclass()
print(a.add(4))
print(a.multiple(2))

b = Bclass()
print(b.add(4))
print(b.val_return())

Pure Python Mode

以上のプロセスを振り返ると,A.pxdがA.pyxから定義だけを抜き出していて冗長のように思われる.Cythonは,元のpythonのコードを丸々残したまま,cにcompileする手法を”.pxd”を通して提供する[7].以下のコードは前の章と”ほぼ”同じ結果になる.

C.py

def plus(x, y):
    return(x + y)

def minus(x,y):
    return(x- y)

class Cclass:
    def __init__(self):
        self.a = 3
    def add(self,x = 3):
        return(self.a + x) 

    def minus(self):
        y = 2
        return(self.a - y )

    def multiple(self,x):
        return(self.a*x)

C.pxd, クラス内の cdef int a にpublicは必要ない.”.pxd”内でクラス内の変数宣言は全てpublicとして取り扱われるからだ.

# cython: language_level=2
cdef double plus(double ,double)
cpdef double minus(double x, double y )

cdef class Cclass:
    cdef int a 
    cdef double add(self,double x =*)
    cpdef double minus(self)

setup2.py , (複数のファイルをcompileする方法の紹介のために一つ前のファイルもcompileするようにしている.)

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

ext_modules = [ 
            Extension("A", ["A.pyx"]),
            Extension("C", ["C.py"])
            ]

setup( name = "Myproject", 
        ext_modules =  cythonize(ext_modules,annotate=True)
)

同様に python setup2.py build_ext --inplace を実行する.compileの結果は以下のようになる.

先に”ほぼ”と言ったのは,クラス”Cclass”のメソッド”minus”の下2行が高速化されていない点だ.というのもC.pxdではメソッド内での変数の型宣言の方法が分からなかった… (インデントを入れて宣言するとエラーが出る)

Pure Python Modeを用いることで,(他にもmagic attributionを使う方法も合わせて)pythonの原型のコードを残した状態でcにcompileすることが可能である.

———-雑感(`・ω・´)———-
二つの方法を紹介したが,個人的には前者をお勧めする.というのも,jupyter notebook上で試行錯誤した上でそのまま,ファイルを移せばいちいちcompileする必要性もないからだ.また,Pure Python Modeだとクラスのメソッド内が高速化出来ない可能性が浮上するからだ(magic atrributionを使えば解決可能?). 
とりあえず,これでjupyter notebook上のコードをスッキリさせることが出来る!  

参考文献

[1] Language Basics, Cython file types, https://cython2.readthedocs.io/en/stable/src/userguide/language_basics.html
[2] pxd files, https://cython.readthedocs.io/en/latest/src/tutorial/pxd_files.html
[3] Sharing Declarations Between Cython Modules, https://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html
[4] Source Files and Compilation, https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html
[5] Cython setup.py for several .pyx, https://stackoverflow.com/questions/21826137/cython-setup-py-for-several-pyx
[6] Cython -a flag (to generate yellow-shaded HTML) without command line, https://stackoverflow.com/questions/11058933/cython-a-flag-to-generate-yellow-shaded-html-without-command-line
[7] Pure Python Mode, https://cython.readthedocs.io/en/latest/src/tutorial/pure.html#augmenting-pxd



コメント

  1. […] cythonのコードを外部からimportする方法 を先に読むとcython独特の記法が把握出来る. コードは,git clone https://github.com/akitoshiblog/blogsiteでmcmc_cythonの下にある. […]

タイトルとURLをコピーしました