web-dev-qa-db-fra.com

Comment dois-je structurer un Python qui contient du code Cython

Je voudrais faire un Python paquet contenant du code Cython . J'ai le code Cython qui fonctionne bien. Cependant, maintenant je veux savoir comment mieux pour l'emballer.

Pour la plupart des gens qui souhaitent simplement installer le package, j'aimerais inclure le .c fichier créé par Cython et organiser setup.py pour compiler cela pour produire le module. Ensuite, l'utilisateur n'a pas besoin que Cython soit installé pour installer le package.

Mais pour les personnes qui souhaitent modifier le package, je voudrais également fournir le Cython .pyx fichiers, et autorisent également setup.py pour les construire en utilisant Cython (pour que ces utilisateurs aient besoin que Cython soit installé).

Comment dois-je structurer les fichiers dans le package pour répondre à ces deux scénarios?

Le la documentation Cython donne quelques conseils . Mais il ne dit pas comment faire un seul setup.py qui gère à la fois les cas avec/sans Cython.

117
Craig McQueen

Je l'ai fait moi-même maintenant, dans un Python simplerandom ( dépôt BitBucket - EDIT: maintenant - github ) (Je ne m'attends pas à ce que ce soit un package populaire, mais c'était une bonne chance d'apprendre Cython).

Cette méthode repose sur le fait que la construction d'un .pyx fichier avec Cython.Distutils.build_ext (au moins avec Cython version 0.14) semble toujours créer un .c fichier dans le même répertoire que la source .pyx fichier.

Voici une version abrégée de setup.py qui j'espère montre l'essentiel:

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,
    ...
)

J'ai également édité MANIFEST.in pour être sur de mycythonmodule.c est inclus dans une distribution source (une distribution source créée avec python setup.py sdist):

...
recursive-include cython *
...

Je ne commets pas mycythonmodule.c pour contrôler la version 'tronc' (ou 'par défaut' pour Mercurial). Quand je fais une version, je dois me rappeler de faire un python setup.py build_ext d'abord, pour garantir que mycythonmodule.c est présent et à jour pour la distribution du code source. Je crée également une branche de publication et je valide le fichier C dans la branche. De cette façon, j'ai un historique du fichier C qui a été distribué avec cette version.

66
Craig McQueen

Ajout à la réponse de Craig McQueen: voir ci-dessous pour savoir comment remplacer la commande sdist pour que Cython compile automatiquement vos fichiers source avant de créer une distribution source.

De cette façon, vous n'avez aucun risque de distribuer accidentellement des sources C obsolètes. Cela aide également dans le cas où vous avez un contrôle limité sur le processus de distribution, par exemple lors de la création automatique de distributions à partir d'une intégration continue, etc.

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
kynan

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

Il est fortement recommandé de distribuer les fichiers .c générés ainsi que vos sources Cython, afin que les utilisateurs puissent installer votre module sans avoir besoin d'avoir Cython disponible.

Il est également recommandé que la compilation Cython ne soit pas activée par défaut dans la version que vous distribuez. Même si l'utilisateur a installé Cython, il ne veut probablement pas l'utiliser uniquement pour installer votre module. De plus, la version qu'il possède peut ne pas être la même que celle que vous avez utilisée et peut ne pas compiler correctement vos sources.

Cela signifie simplement que le fichier setup.py que vous expédiez avec sera juste un fichier distutils normal sur les fichiers .c générés, pour l'exemple de base que nous aurions à la place:

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

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

Le plus simple est d'inclure les deux mais d'utiliser simplement le fichier c? Inclure le fichier .pyx est bien, mais ce n'est pas nécessaire une fois que vous avez le fichier .c de toute façon. Les personnes qui souhaitent recompiler le .pyx peuvent installer Pyrex et le faire manuellement.

Sinon, vous devez avoir une commande build_ext personnalisée pour distutils qui crée le fichier C en premier. Cython en inclut déjà un. http://docs.cython.org/src/userguide/source_files_and_compilation.html

Ce que cette documentation ne fait pas, c'est de dire comment rendre cela conditionnel, mais

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

Devrait le gérer.

7
Lennart Regebro

L'inclusion de fichiers .c générés (Cython) est assez bizarre. Surtout quand nous incluons cela dans git. Je préfère utiliser setuptools_cython . Lorsque Cython n'est pas disponible, il construira un Egg qui a un environnement Cython intégré, puis construira votre code en utilisant Egg.

Un exemple possible: https://github.com/douban/greenify/blob/master/setup.py


Mise à jour (2017-01-05):

Puisque setuptools 18.0, il n'est pas nécessaire d'utiliser setuptools_cython. Ici est un exemple pour construire un projet Cython à partir de zéro sans setuptools_cython.

4
McKelvin

Le simple hack que j'ai trouvé:

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

Installez simplement Cython s'il n'a pas pu être importé. On ne devrait probablement pas partager ce code, mais pour mes propres dépendances c'est assez bon.

2
kay

Il s'agit d'un script de configuration que j'ai écrit qui facilite l'inclusion de répertoires imbriqués dans la build. Il faut l'exécuter à partir du dossier d'un package.

Structure Givig comme ceci:

__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

Bonne compilation;)

2
zzart

Le moyen le plus simple que j'ai trouvé en utilisant uniquement setuptools au lieu des fonctionnalités limitées est

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)
1
MosteM

Toutes les autres réponses reposent soit sur

  • distutils
  • importation depuis Cython.Build, ce qui crée un problème de poulet et d'oeuf entre la nécessité de cython via setup_requires et l'importer.

Une solution moderne consiste à utiliser setuptools à la place, voir cette réponse (la gestion automatique des extensions Cython nécessite setuptools 18.0, c'est-à-dire qu'il est disponible depuis de nombreuses années déjà). Un standard moderne setup.py avec la gestion des exigences, un point d'entrée et un module cython pourrait ressembler à ceci:

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
bluenote10