Как мне структурировать пакет Python, содержащий код Cython - PullRequest
114 голосов
/ 22 декабря 2010

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

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

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

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

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

Ответы [ 9 ]

63 голосов
/ 23 декабря 2010

Я сделал это сам сейчас, в пакете Python simplerandom ( Репозиторий BitBucket - РЕДАКТИРОВАТЬ: сейчас github ) (я не ожидайте, что это будет популярный пакет, но это был хороший шанс изучить Cython).

Этот метод основан на том факте, что при создании файла .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, чтобы убедиться, что mycythonmodule.c присутствует и актуален для распространения исходного кода. Я также делаю ветку релиза и фиксирую файл C в ветке. Таким образом, у меня есть историческая запись файла C, который был распространен с этим выпуском.

18 голосов
/ 24 августа 2013

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

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"])]
)
7 голосов
/ 22 декабря 2010

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

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

Должен справиться с этим.

4 голосов
/ 11 декабря 2014

Включая (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.

2 голосов
/ 18 мая 2016

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

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 голосов
/ 24 июня 2014

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

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

Удачной компиляции;)

1 голос
/ 30 июля 2018

Все остальные ответы либо основаны на

  • 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'],
        ),
    ],
)
1 голос
/ 26 января 2018

Самый простой способ, который я нашел, используя только 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)
...