Как я должен структурировать пакет Python, содержащий код Cython
Я хотел бы сделать пакет Python, содержащий некоторые на Cython код. У меня есть код Цитона работает хорошо. Однако теперь я хочу знать, как лучше всего упаковать его.
для большинства людей, которые просто хотят установить пакет, хочу включить .c файл, который создает Cython, и организовать для setup.py чтобы скомпилировать это для создания модуля. Тогда пользователю не нужно устанавливать Cython для установки пакета.
но для людей, которые могут хотите изменить пакет, я также хотел бы предоставить Cython .pyx файлы, и как-то также позволяют setup.py чтобы построить их с помощью Cython (так что эти пользователи б нужно установить Cython).
как я должен структурировать файлы в пакете для удовлетворения обоих этих сценариев?
The документация Cython дает небольшое руководство. Но он не говорит, Как сделать один setup.py который обрабатывает оба случая с / без Cython.
9 ответов:
я сделал это сам сейчас, в пакете Python
simplerandom(BitBucket repo - EDIT: сейчас github) (я не ожидаю, что это будет популярный пакет, но это был хороший шанс узнать на Cython).этот метод основан на том, что при строительстве С
Cython.Distutils.build_ext(по крайней мере, с Cython версии 0.14) всегда кажется, чтобы создать.cфайл в том же каталоге, что и исходный .здесь урезанная версия
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, который был распространен с этим выпуском.
добавление к ответу Крейга Маккуина: смотрите ниже, как переопределить
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/src/reference/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"])] )
проще всего включить оба, но просто использовать 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должны справиться с этим.
в том числе (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.
это сценарий установки, который я написал, что облегчает включение вложенных каталогов внутри сборки. Нужно запустить его из папки в пакете.
структура дающими такой:
__init__.py setup.py test.py subdir/ __init__.py anothertest.pysetup.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счастливый компиляции ;)
простой хак, который я придумал:
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, если он не может быть импортирован. Вероятно, не следует делиться этим кодом, но для моих собственных зависимостей это достаточно хорошо.
самый простой способ, который я нашел, используя только setuptools вместо функции limited 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)
все остальные ответы либо положиться на
- distutils
- импорт из
Cython.Build, что создает проблему курицы и яйца между требованием цитона через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'], ), ], )
Comments