Cython メモ

2020年4月26日

はじめに

Cython メモ。

環境

  • Anaconda3

単体のスクリプトのコンパイル

たとえば、main.py を書くとして、実装を impl.py に分ける。

main.py

from impl import *

コンパイル

$ cythonize -b -3 impl.py

モジュール

たとえば、モジュール mymod を考える。

mymod/__init__.py

from mymod.impl from *

impl.py に実装を書く。

コンパイルは次のようにする。

script.py

from distutils.core import setup
from Cython.Distutils import build_ext
from Cython.Distutils.extension import Extension
from pathlib import Path
import os
import shutil as sh

root = "mymod"

def getExtensions(root):
    r = []
    for path in list(Path(root).glob("**/impl.py")):
        name = str(path)[:-3]
        name = name.replace(os.path.sep, ".")
        e = Extension(
            name,
            [str(path)],
            cython_directives={"language_level": 3}
        )
        r.append(e)
    return r

extensions = getExtensions(root)

setup(
    cmdclass={"build_ext": build_ext},
    ext_modules=extensions
)

root でモジュール名を指定している。

コンパイル

$ python setup.py build_ext --inplace

実行に必要なものだけを残したい場合は、最後に以下のコードを追加すればよい。コメントを外すと Python スクリプトも削除するので注意。コンパイル用にモジュールのコードを別のところにコピーしてコンパイルする。

for e in extensions:
    base = e.name.replace(".", os.path.sep)
    #os.remove(base + ".py") # 注意: Python スクリプトを削除!
    os.remove(base + ".c")

for path in list(Path(root).glob("**/*")):
    if path.name == "__pycache__":
        sh.rmtree(path)

コンパイルオプションを一部置き換えたい場合

コンパイル時、次のようなエラーが出た例があた。

this linker was not configured to use sysroots

gcc の "-Wl,--sysroot=/" オプションが問題のようで、これは、binutils が "--with-sysroot" が有効な状態でビルドされていないためらしい。"-Wl,--sysroot=/" オプションを取り除くために、setup.py の先頭に次のように追加する。

setup.py

from distutils.sysconfig import get_config_vars as get_config_vars_default

def remove_option(x, opt):
    if type(x) is str:
        if x == opt:
            return ""
        if x.startswith("%s " % opt):
            return remove_option(x[len("%s " % opt):], opt)
        if x.endswith(" %s" % opt):
            return remove_option(x[:-len(" %s" % opt)], opt)
        return x.replace(" %s " % opt, " ")
    return x

def my_get_config_vars(*args):
    opt = "-Wl,--sysroot=/"
    result = get_config_vars_default(*args)
    if type(result) is list:
         return [remove_option(x, opt) for x in result]
    elif type(result) is dict:
         return {k : remove_option(x, opt) for k, x in result.items()}
    else:
         raise Exception("cannot handle type" + type(result))

import distutils.sysconfig as dsc
dsc.get_config_vars = my_get_config_vars

# このあと通常の setup.py の内容が続く

my_get_config_vars() の opt 変数で無効にするオプションを指定している (この例では指定できるオプションはは 1 つのみ)。

参考