Cythonコードを含むPythonパッケージをどのように構成すればよいですか


122

Cythonコードを含むPythonパッケージを作成したいのですが。Cythonコードが適切に機能しています。しかし、私はそれをどのようにパッケージ化するのが最善かを知りたいです。

パッケージをインストールしたいほとんどの人のために、.cCythonが作成したファイルを含め、それsetup.pyをコンパイルしてモジュールを生成するように手配したいと思います。その場合、ユーザーはパッケージをインストールするためにCythonをインストールする必要はありません。

しかし、パッケージを変更したい人のために、Cython .pyxファイルも提供し、Cython setup.pyを使用してそれらをビルドできるようにします(そのため、これらのユーザー Cythonをインストールする必要あります)。

これらの両方のシナリオに対応するには、パッケージ内のファイルをどのように構成すればよいですか?

Cythonのドキュメントには、小さなガイダンスが記載されています。しかし、setup.pyCythonの有無の両方のケースを処理するシングルをどのように作成するかについては触れていません。


1
私の質問は、どの回答よりも多くの賛成票を得ているようです。なぜ人々が答えを満足できないと思うのか知りたいです。
クレイグマックイーン

4
私は正確に答えを与えるドキュメントのこのセクションを見つけました。
2014

回答:


72

私はこれを自分でPythonパッケージsimplerandomBitBucket repo-編集:github)で行いました(これが人気のあるパッケージであるとは思っていませんが、Cythonを学ぶ良い機会でした)。

この方法は.pyxCython.Distutils.build_ext(少なくともCythonバージョン0.14で)ファイルをビルドすると、常に.cソース.pyxファイルと同じディレクトリにファイルが作成されるように見えるという事実に依存しています。

ここにsetup.py私が本質を示すことを願うカットダウンバージョンがあります:

from distutils.core import setup
from distutils.extension import Extension

try:
    from Cython.Distutils import build_ext
except ImportError:
    use_cython = False
else:
    use_cython = True

cmdclass = {}
ext_modules = []

if use_cython:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.pyx"]),
    ]
    cmdclass.update({'build_ext': build_ext})
else:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.c"]),
    ]

setup(
    name='mypackage',
    ...
    cmdclass=cmdclass,
    ext_modules=ext_modules,
    ...
)

また、ソース配布(で作成されたソース配布)MANIFEST.inに確実にmycythonmodule.c含まれるように編集しましたpython setup.py sdist

...
recursive-include cython *
...

私はmycythonmodule.cバージョン管理「トランク」(またはMercurialの「デフォルト」)にコミットしません。リリースを作成するときは、ソースコードの配布が最新で最新であるpython setup.py build_extことを確認するために、最初にリリースすることを忘れないでくださいmycythonmodule.c。また、リリースブランチを作成し、Cファイルをブランチにコミットします。そうすれば、そのリリースで配布されたCファイルの履歴レコードを手に入れることができます。


おかげで、これはまさに私が開いているPyrexプロジェクトに必要なものです!MANIFEST.inは1秒間トリップしましたが、必要なのは1行だけでした。興味のないソースコントロールにCファイルを含めていますが、それは不要であるという点を理解しています。
chmullig

Cファイルがトランク/デフォルトにないが、リリースブランチに追加される方法を説明するために私の回答を編集しました。
Craig McQueen、

1
@CraigMcQueen素晴らしい答えをありがとう、それは私をたくさん助けてくれました!しかし、可能であればCythonを使用するのは望ましい動作ですか?ユーザーが明示的にCythonを使用したい場合を除いて、デフォルトで事前に生成されたcファイルを使用する方がよいように思えます。その場合、ユーザーは環境変数などを設定できます。ユーザーはインストールしたCythonのバージョンに基づいて異なる結果を得る可能性があるため、インストールはより安定した/堅牢なものになります。インストール済みであり、パッケージのビルドに影響していることさえ知らない可能性があります。
マルティノス2017

20

Craig McQueenの回答への追加:sdistCythonがソース配布を作成する前にソースファイルを自動的にコンパイルするようにコマンドをオーバーライドする方法については、以下を参照してください。

そうすれば、古いCソースを誤って配布するリスクがなくなります。また、継続的な統合から自動的に配布を作成する場合など、配布プロセスの制御が制限されている場合にも役立ちます。

from distutils.command.sdist import sdist as _sdist

...

class sdist(_sdist):
    def run(self):
        # Make sure the compiled Cython files in the distribution are up-to-date
        from Cython.Build import cythonize
        cythonize(['cython/mycythonmodule.pyx'])
        _sdist.run(self)
cmdclass['sdist'] = sdist

19

http://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules

ユーザーがCythonを使用可能にする必要なくモジュールをインストールできるように、生成された.cファイルとCythonソースを配布することを強くお勧めします。

また、配布するバージョンでは、デフォルトでCythonコンパイルを有効にしないことをお勧めします。ユーザーがCythonをインストールしていても、モジュールをインストールするためだけにそれを使用することは望ましくないでしょう。また、彼が使用しているバージョンは、使用しているバージョンと異なる可能性があり、ソースを正しくコンパイルできない可能性があります。

これは単に、付属のsetup.pyファイルが、生成された.cファイルの通常のdistutilsファイルであることを意味します。代わりに、基本的な例を示します。

from distutils.core import setup
from distutils.extension import Extension
 
setup(
    ext_modules = [Extension("example", ["example.c"])]
)

7

両方を含めるのが最も簡単ですが、cファイルを使用するだけですか?.pyxファイルを含めると便利ですが、.cファイルがあれば必要ありません。.pyxを再コンパイルしたい人はPyrexをインストールして手動で実行できます。

それ以外の場合は、最初にCファイルをビルドするdistutils用のカスタムbuild_extコマンドが必要です。Cythonにはすでに1つ含まれています。http://docs.cython.org/src/userguide/source_files_and_compilation.html

そのドキュメントがしていないことは、これを条件付きにする方法を言うことですが、

try:
     from Cython.distutils import build_ext
except ImportError:
     from distutils.command import build_ext

それを処理する必要があります。


1
ご回答有難うございます。Cythonがインストールされている場合setup.py.pyxファイルから直接ビルドできるのが望ましいのですが、それは妥当です。私の答えもそれを実装しています。
Craig McQueen

それが私の答えの要点です。これは完全なsetup.pyではありませんでした。
Lennart Regebro 2010

4

(Cython)で生成された.cファイルを含めるのはかなり奇妙です。特にそれをgitに含める場合。setuptools_cythonを使用したいと思います。Cythonが利用できない場合は、Cython環境が組み込まれたeggをビルドし、eggを使用してコードをビルドします。

考えられる例:https : //github.com/douban/greenify/blob/master/setup.py


アップデート(2017-01-05):

以降setuptools 18.0、を使用する必要はありませんsetuptools_cythonこれは、Cythonプロジェクトをなしでゼロからビルドする例setuptools_cythonです。


これは、setup_requiresで指定してもCythonがインストールされない問題を修正しますか?
カミルシンディ2017年

'setuptools>=18.0'メソッドを作成する代わりにsetup_requires を入れることもできませんis_installedか?
カミルシンディ2017年

1
@capitalistpugまずsetuptools>=18.0、インストールが完了していることを確認する必要があります。次にを入力するだけ'Cython >= 0.18'setup_requires、インストールの進行中にCythonがインストールされます。ただし、setuptools <18.0を使用している場合、setup_requiresに特定のcythonがあっても、インストールされませんsetuptools_cython。この場合は、使用を検討してください。
マッケルビン2017年

@McKelvinに感謝します。これは素晴らしい解決策のようです!これの前に、ソースファイルを事前にCythonizeして、他のアプローチを使用する必要がある理由はありますか?私はあなたのアプローチを試みました、そしてそれはインストールするときいくらか遅いように見えます(インストールするのに1分かかりますが、1秒でビルドします)。
Martinsos

1
@Martinsos pip install wheel。それが理由1である必要があります。まずホイールを取り付けてから、もう一度試してください。
マッケルビン2017

2

これは私が書いたセットアップスクリプトで、ビルド内にネストされたディレクトリを簡単に含めることができます。パッケージ内のフォルダーから実行する必要があります。

このようなGivig構造:

__init__.py
setup.py
test.py
subdir/
      __init__.py
      anothertest.py

setup.py

from setuptools import setup, Extension
from Cython.Distutils import build_ext
# from os import path
ext_names = (
    'test',
    'subdir.anothertest',       
) 

cmdclass = {'build_ext': build_ext}
# for modules in main dir      
ext_modules = [
    Extension(
        ext,
        [ext + ".py"],            
    ) 
    for ext in ext_names if ext.find('.') < 0] 
# for modules in subdir ONLY ONE LEVEL DOWN!! 
# modify it if you need more !!!
ext_modules += [
    Extension(
        ext,
        ["/".join(ext.split('.')) + ".py"],     
    )
    for ext in ext_names if ext.find('.') > 0]

setup(
    name='name',
    ext_modules=ext_modules,
    cmdclass=cmdclass,
    packages=["base", "base.subdir"],
)
#  Build --------------------------
#  python setup.py build_ext --inplace

幸せなコンパイル;)


2

私が思いついた簡単なハック:

from distutils.core import setup

try:
    from Cython.Build import cythonize
except ImportError:
    from pip import pip

    pip.main(['install', 'cython'])

    from Cython.Build import cythonize


setup(…)

インポートできない場合は、Cythonをインストールしてください。おそらくこのコードを共有すべきではありませんが、私自身の依存関係には十分です。


2

他のすべての回答は、

  • distutils
  • からのインポートCython.Build。これにより、cython via setup_requiresを必要とすることとそれをインポートすることの間に鶏と卵の問題が生じます。

最新の解決策は、代わりにsetuptoolsを使用することです。この回答を参照してください(Cython拡張機能の自動処理にはsetuptools 18.0が必要です。つまり、すでに何年も利用可能です)。setup.py要件処理、エントリポイント、およびcythonモジュールを備えた最新の標準は、次のようになります。

from setuptools import setup, Extension

with open('requirements.txt') as f:
    requirements = f.read().splitlines()

setup(
    name='MyPackage',
    install_requires=requirements,
    setup_requires=[
        'setuptools>=18.0',  # automatically handles Cython extensions
        'cython>=0.28.4',
    ],
    entry_points={
        'console_scripts': [
            'mymain = mypackage.main:main',
        ],
    },
    ext_modules=[
        Extension(
            'mypackage.my_cython_module',
            sources=['mypackage/my_cython_module.pyx'],
        ),
    ],
)

Cython.Buildセットアップ時にからインポートすると、ImportErrorが発生します。pyxをコンパイルするためのsetuptoolsを用意することは、それを行うための最良の方法です。
Carson Ip

1

機能が限定されたdistutilsの代わりにsetuptoolsのみを使用して見つけた最も簡単な方法は、

from setuptools import setup
from setuptools.extension import Extension
try:
    from Cython.Build import cythonize
except ImportError:
    use_cython = False
else:
    use_cython = True

ext_modules = []
if use_cython:
    ext_modules += cythonize('package/cython_module.pyx')
else:
    ext_modules += [Extension('package.cython_module',
                              ['package/cython_modules.c'])]

setup(name='package_name', ext_modules=ext_modules)

実際、setuptoolsでは、からの明示的なtry / catched importの必要はありませんCython.Build。私の回答を参照してください。
bluenote10 2018

0

カスタムbuild_extコマンドを提供することで、これを行うためのかなり良い方法を見つけたと思います。アイデアは次のとおりです。

  1. 関数の本体でオーバーライドfinalize_options()して実行することでnumpyヘッダーを追加しますimport numpy。これにより、numpyがsetup()インストールされる前に使用できないという問題がうまく回避されます。

  2. システムでcythonが使用可能な場合、それはコマンドのcheck_extensions_list()メソッドにフックし、古いcythonモジュールをすべてcythonizeし、後でbuild_extension() メソッドで処理できるC拡張で置き換えます。モジュールの機能の後半部分も提供するだけです。つまり、cythonが利用できないがC拡張が存在する場合でも機能し、ソース配布を行うことができます。

これがコードです:

import re, sys, os.path
from distutils import dep_util, log
from setuptools.command.build_ext import build_ext

try:
    import Cython.Build
    HAVE_CYTHON = True
except ImportError:
    HAVE_CYTHON = False

class BuildExtWithNumpy(build_ext):
    def check_cython(self, ext):
        c_sources = []
        for fname in ext.sources:
            cname, matches = re.subn(r"(?i)\.pyx$", ".c", fname, 1)
            c_sources.append(cname)
            if matches and dep_util.newer(fname, cname):
                if HAVE_CYTHON:
                    return ext
                raise RuntimeError("Cython and C module unavailable")
        ext.sources = c_sources
        return ext

    def check_extensions_list(self, extensions):
        extensions = [self.check_cython(ext) for ext in extensions]
        return build_ext.check_extensions_list(self, extensions)

    def finalize_options(self):
        import numpy as np
        build_ext.finalize_options(self)
        self.include_dirs.append(np.get_include())

これによりsetup()、インポートやcythonが利用可能かどうかを心配することなく、引数を書き込むことができます。

setup(
    # ...
    ext_modules=[Extension("_my_fast_thing", ["src/_my_fast_thing.pyx"])],
    setup_requires=['numpy'],
    cmdclass={'build_ext': BuildExtWithNumpy}
    )
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.