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


122

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

패키지 만 설치하려는 대부분의 사람들을 위해 .cCython이 생성 한 파일 을 포함 setup.py하고 모듈을 생성 하기 위해 컴파일 하도록 준비 하고 싶습니다 . 그러면 사용자는 패키지를 설치하기 위해 Cython을 설치할 필요가 없습니다.

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

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

사이 썬 문서는 약간의 지침을 제공합니다 . 하지만 싱글을 만드는 방법은 나와 있지 않습니다.setup.py Cython의 유무에 관계없이 모두를 처리 .


1
나는 질문이 어떤 답변보다 더 많은 찬성표를 얻고 있음을 알고 있습니다. 왜 사람들이 불만족스러운 답을 찾을 수 있는지 궁금합니다.
Craig McQueen

4
나는 정확히 대답을 제공하는 문서의이 섹션을 찾았다 .

답변:


72

나는 파이썬 패키지 simplerandom( BitBucket repo- 편집 : 이제 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버전 관리 '트렁크'(또는 Mercurial의 경우 '기본값')에 전념하지 않습니다 . 릴리스를 만들 때 소스 코드 배포를 위해 최신 상태로 존재 python setup.py build_ext하는지 확인하기 위해 첫 번째 작업을 수행해야합니다 mycythonmodule.c. 릴리스 브랜치를 만들고 C 파일을 브랜치에 커밋합니다. 그렇게하면 해당 릴리스와 함께 배포 된 C 파일의 기록 기록이 있습니다.


감사합니다. 이것이 제가 공개하고있는 Pyrex 프로젝트에 필요한 것입니다! MANIFEST.in이 잠시 나를 넘어갔지 만 그 한 줄이 필요했습니다. 관심이 없어서 소스 제어에 C 파일을 포함하고 있지만 불필요하다는 점을 알고 있습니다.
chmullig 2011 년

C 파일이 트렁크 / 기본값에 없지만 릴리스 분기에 추가되는 방법을 설명하기 위해 내 대답을 편집했습니다.
Craig McQueen

1
@CraigMcQueen 훌륭한 답변에 감사드립니다. 그러나 가능한 경우 Cython을 사용하는 것이 바람직한 동작입니까? 사용자가 명시 적으로 Cython을 사용하기를 원하지 않는 한 기본적으로 미리 생성 된 c 파일을 사용하는 것이 더 나을 것 같습니다.이 경우 환경 변수 등을 설정할 수 있습니다. 사용자가 설치 한 Cython 버전에 따라 다른 결과를 얻을 수 있기 때문에 설치가보다 안정적이고 견고 해집니다. 사용자는 설치 한 Cython이 패키지 빌드에 영향을 미치는지 알지 못할 수도 있습니다.
Martinsos

20

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

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에는 이미 하나가 포함되어 있습니다.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 환경이 내장 된 달걀을 빌드 한 다음 달걀을 사용하여 코드를 빌드합니다.

가능한 예 : https://github.com/douban/greenify/blob/master/setup.py


업데이트 (2017-01-05) :

이후 setuptools 18.0, 사용할 필요가 없습니다 setuptools_cython. 다음 은 .NET없이 Cython 프로젝트를 처음부터 빌드하는 예제 setuptools_cython입니다.


setup_requires에서 지정하더라도 Cython이 설치되지 않는 문제를 해결합니까?
Kamil Sindi

또한 'setuptools>=18.0'메서드를 만드는 대신 setup_requires 를 넣을 수 is_installed없습니까?
Kamil Sindi

1
@capitalistpug 먼저 당신이 있는지 확인해야합니다 setuptools>=18.0당신은 넣을 필요가 설치되어 'Cython >= 0.18'있는 setup_requires, 그리고 사이 썬이 진행을 설치할 때 설치됩니다. 그러나 setuptools <18.0을 사용하는 경우 setup_requires에서 특정 cython을 사용하더라도 설치되지 않습니다.이 경우 사용을 고려해야합니다 setuptools_cython.
McKelvin

@McKelvin에게 감사드립니다. 이것은 훌륭한 솔루션으로 보입니다! 이 옆에 소스 파일을 미리 cythonizing하는 다른 접근 방식을 사용해야하는 이유가 있습니까? 나는 당신의 접근 방식을 시도했고 설치할 때 다소 느린 것 같습니다 (설치하는 데 1 분이 걸리지 만 1 초 안에 빌드됩니다).
Martinsos

1
@Martinsos pip install wheel. 그렇다면 이유 1이어야합니다. 먼저 휠을 설치하고 다시 시도하십시오.
McKelvin

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.Buildcython을 통해 필요한 것과 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를 컴파일하는 설정 도구를 사용하는 것이 가장 좋은 방법입니다.
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 가져 오기가 필요하지 않습니다 Cython.Build. 내 대답을 참조하십시오.
bluenote10

0

사용자 지정 build_ext명령 을 제공하여이 작업을 수행하는 좋은 방법을 찾은 것 같습니다 . 아이디어는 다음과 같습니다.

  1. 함수 본문을 재정의 finalize_options()하고 수행 하여 numpy 헤더를 추가합니다. 이렇게 import numpy하면 setup()설치 하기 전에 사용할 수없는 numpy 문제를 방지 할 수 있습니다 .

  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.