Подтвердить что ты не робот

Как я должен создать пакет Python, содержащий код Cython

Я хотел бы сделать пакет Python, содержащий некоторый Cython код. У меня код Cython работает красиво. Однако теперь я хочу знать, как лучше всего упаковать его.

Для большинства людей, которые просто хотят установить пакет, я хотел бы добавить файл .c, который создает Cython, и организовать setup.py для компиляции этого для создания модуля. Тогда пользователю не нужен Cython для установки пакета.

Но для людей, которые могут захотеть изменить пакет, я также хотел бы предоставить файлы Cython .pyx и как-то также разрешить setup.py создавать их с помощью Cython (чтобы таким пользователям были нужны Cython).

Как мне структурировать файлы в пакете для обслуживания обоих этих сценариев?

Документация Cython дает небольшое руководство. Но он не говорит, как сделать один setup.py, который обрабатывает как с/без случаев Cython.

4b9b3361

Ответ 1

Я сделал это сам сейчас, в simplerandom пакете simplerandom (BitBucket repo - EDIT: теперь github) (я не ожидаю, что это будет популярный пакет, но это был хороший шанс изучить Cython).

Этот метод основан на том факте, что при .pyx файла .pyx с помощью Cython.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 для контроля версий 'trunk' (или 'default' для Mercurial). Когда я делаю релиз, мне нужно не python setup.py build_ext сначала выполнить python setup.py build_ext, чтобы обеспечить наличие mycythonmodule.c и его актуальность для распространения исходного кода. Я также делаю ветку релиза и фиксирую файл C в ветке. Таким образом, у меня есть историческая запись файла C, который был распространен с этим выпуском.

Ответ 2

Добавление к ответу Крейга МакКуина: см. ниже, как переопределить команду 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

Ответ 3

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

Настоятельно рекомендуется распространять сгенерированные файлы .c, а также исходные коды Cython, чтобы пользователи могли устанавливать ваш модуль без необходимости иметь Cython.

Также рекомендуется, чтобы компиляция Cython не была включена по умолчанию в распространяемой вами версии. Даже если у пользователя установлен Cython, он, вероятно, не хочет использовать его просто для установки вашего модуля. Кроме того, версия, которую он имеет, может отличаться от используемой вами, и может некорректно компилировать ваши исходные коды.

Это просто означает, что файл setup.py, который вы поставляете, будет просто обычным файлом distutils для сгенерированных файлов .c, для базового примера, который мы имеем вместо этого:

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

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

Ответ 4

Проще всего включить оба, но просто использовать c файл? Включение файла .pyx является приятным, но это не нужно, если у вас есть .c файл в любом случае. Люди, которые хотят перекомпилировать .pyx, могут установить Pyrex и сделать это вручную.

В противном случае вам нужно создать пользовательскую команду build_ext для distutils, которая сначала создаст файл C. Китон уже включает один. 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

Должен обрабатывать его.

Ответ 5

В том числе (Cython) сгенерированные файлы .c довольно странно. Особенно, когда мы включаем это в git. Я бы предпочел использовать setuptools_cython. Когда Cython недоступен, он построит яйцо со встроенной средой Cython, а затем построит ваш код с помощью яйца.

Возможный пример: https://github.com/douban/greenify/blob/master/setup.py


Обновление (2017-01-05):

Так как setuptools 18.0, нет необходимости использовать setuptools_cython. Здесь приведен пример создания проекта Cython с нуля без setuptools_cython.

Ответ 6

Это настройка script, которую я написал, что упрощает включение вложенных каталогов внутри сборки. Нужно запустить его из папки внутри пакета.

Структура 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

Счастливая компиляция;)

Ответ 7

Простой взлом я придумал:

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, если он не может быть импортирован. Вероятно, не стоит делиться этим кодом, но для моих собственных зависимостей это достаточно хорошо.

Ответ 8

Самый простой способ, который я нашел, используя только setuptools вместо distutils с ограниченными возможностями, это

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)

Ответ 9

Все остальные ответы либо полагаются на

  • Distutils
  • импортирование из Cython.Build, которое создает проблему "курицы и яйца" между требованием использования cython через 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'],
        ),
    ],
)