IT story

Cython 코드가 포함 된 Python 패키지를 어떻게 구성해야합니까

hot-time 2020. 7. 19. 09:13
반응형

Cython 코드가 포함 된 Python 패키지를 어떻게 구성해야합니까


Cython 코드가 포함 된 Python 패키지를 만들고 싶습니다 . Cython 코드가 훌륭하게 작동합니다. 그러나 이제는 그것을 포장하는 가장 좋은 방법을 알고 싶습니다.

패키지를 설치하려는 대부분의 사람들에게 .cCython이 만든 파일 을 포함 setup.py시키고 모듈을 만들기 위해 컴파일 할 준비를 하고 싶습니다 . 그런 다음 패키지를 설치하기 위해 Cython을 설치할 필요가 없습니다.

그러나 패키지를 수정하려는 사람들을 위해 Cython .pyx파일 을 제공하고 Cython을 setup.py사용하여 빌드 할 수도 있습니다 (따라서 사용자 Cython이 설치되어 있어야 함).

두 시나리오 모두를 충족시키기 위해 패키지의 파일을 어떻게 구성해야합니까?

사이 썬 문서는 약간의 지침을 제공합니다 . 그러나 setup.pyCython의 유무에 관계없이 단일을 만드는 방법을 말하지 않습니다 .


파이썬 패키지 simplerandom( BitBucket repo -EDIT : now github ) 에서이 작업을 직접 수행했습니다 (이 패키지 는 인기있는 패키지는 아니지만 Cython을 배울 수있는 좋은 기회였습니다).

이 방법은 (적어도 Cython 버전 0.14로) .pyx파일 을 빌드하면 Cython.Distutils.build_ext항상 .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.cpython setup.py sdist

...
recursive-include cython *
...

mycythonmodule.c버전 관리 'trunk'(또는 Mercurial의 경우 'default')를 사용 하지 않습니다 . 릴리스 할 때 소스 코드 배포를 위해 최신 상태 python setup.py build_ext인지 확인하려면 먼저 수행해야합니다 mycythonmodule.c. 또한 릴리스 브랜치를 만들고 C 파일을 브랜치에 커밋합니다. 그렇게하면 해당 릴리스와 함께 배포 된 C 파일에 대한 기록이 있습니다.


Craig McQueen의 답변에 추가 : sdist소스 배포를 만들기 전에 Cython에서 소스 파일을 자동으로 컴파일 하도록 명령을 재정의하는 방법은 아래를 참조하십시오 .

이렇게하면 오래된 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

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

생성 된 .c 파일과 Cython 소스를 배포하여 사용자가 Cython을 사용할 필요없이 모듈을 설치할 수 있도록하는 것이 좋습니다.

배포 한 버전에서는 Cython 컴파일을 기본적으로 사용하지 않는 것이 좋습니다. 사용자가 Cython을 설치하더라도 모듈을 설치하기 위해 Cython을 사용하고 싶지 않을 것입니다. 또한 그가 사용하고있는 버전이 사용하지 않은 버전 일 수 있으며 소스를 올바르게 컴파일하지 못할 수 있습니다.

This simply means that the setup.py file that you ship with will just be a normal distutils file on the generated .c files, for the basic example we would have instead:

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

setup(
    ext_modules = [Extension("example", ["example.c"])]
)

The easiest is to include both but just use the c-file? Including the .pyx file is nice, but it's not needed once you have the .c file anyway. People who want to recompile the .pyx can install Pyrex and do it manually.

Otherwise you need to have a custom build_ext command for distutils that builds the C file first. Cython already includes one. http://docs.cython.org/src/userguide/source_files_and_compilation.html

What that documentation doesn't do is say how to make this conditional, but

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

Should handle it.


Including (Cython) generated .c files are pretty weird. Especially when we include that in git. I'd prefer to use setuptools_cython. When Cython is not available, it will build an egg which has built-in Cython environment, and then build your code using the egg.

A possible example: https://github.com/douban/greenify/blob/master/setup.py


Update(2017-01-05):

Since setuptools 18.0, there's no need to use setuptools_cython. Here is an example to build Cython project from scratch without setuptools_cython.


This is a setup script I wrote which makes it easier to include nested directories inside the build. One needs to run it from folder within a package.

Givig structure like this:

__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

Happy compiling ;)


The simple hack I came up with:

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(…)

Just install Cython if it could not be imported. One should probably not share this code, but for my own dependencies it's good enough.


The easiest way I found using only setuptools instead of the feature limited distutils is

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)

All other answers either rely on

  • distutils
  • importing from Cython.Build, which creates a chicken-and-egg problem between requiring cython via setup_requires and importing it.

A modern solution is to use setuptools instead, see this answer (automatic handling of Cython extensions requires setuptools 18.0, i.e., it's available for many years already). A modern standard setup.py with requirements handling, an entry point, and a cython module could look like this:

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'],
        ),
    ],
)

참고URL : https://stackoverflow.com/questions/4505747/how-should-i-structure-a-python-package-that-contains-cython-code

반응형