131 votes

Comment structurer un paquet Python qui contient du code Cython ?

J'aimerais créer un paquetage Python contenant quelques Cython code. J'ai réussi à faire fonctionner le code Cython de manière satisfaisante. Cependant, je veux maintenant savoir comment l'empaqueter au mieux.

Pour la plupart des gens qui veulent simplement installer le paquet, j'aimerais inclure le fichier .c que Cython crée, et s'arranger pour que les setup.py de le compiler pour produire le module. L'utilisateur n'a donc pas besoin d'installer Cython pour pouvoir installer le paquet.

Mais pour les personnes qui voudraient modifier le paquet, j'aimerais également fournir le fichier Cython .pyx et, d'une manière ou d'une autre, permettent également setup.py pour les construire en utilisant Cython (afin que les utilisateurs serait doivent avoir Cython installé).

Comment dois-je structurer les fichiers du paquet pour répondre à ces deux scénarios ?

En La documentation de Cython donne quelques indications . Mais il ne dit pas comment faire un seul setup.py qui gère à la fois les cas avec et sans Cython.

2 votes

Je vois que la question obtient plus de votes positifs que les réponses. Je suis curieux de savoir pourquoi les gens peuvent trouver les réponses insatisfaisantes.

4 votes

J'ai trouvé cette section de la documentation ce qui donne la réponse exacte.

80voto

Craig McQueen Points 13194

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

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

Voici une version abrégée de setup.py qui, je l'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 aussi édité MANIFEST.in afin de garantir que mycythonmodule.c est inclus dans une distribution source (une distribution source qui est créée avec python setup.py sdist ) :

...
recursive-include cython *
...

Je ne m'engage pas mycythonmodule.c au contrôle de version 'trunk' (ou 'default' pour Mercurial). Lorsque je fais une version, je dois me souvenir de faire un python setup.py build_ext d'abord, pour s'assurer que mycythonmodule.c est présent et à jour pour la distribution du code source. Je crée également une branche release, et je committe le fichier C dans cette branche. De cette façon, j'ai un enregistrement historique du fichier C qui a été distribué avec cette version.

0 votes

Merci, c'est exactement ce dont j'avais besoin pour un projet Pyrex que je suis en train d'ouvrir ! MANIFEST.in m'a fait perdre la tête pendant une seconde, mais j'avais juste besoin de cette ligne. J'inclus le fichier C dans le contrôle de la source par intérêt, mais je vois que vous pensez que c'est inutile.

0 votes

J'ai modifié ma réponse pour expliquer comment le fichier C n'est pas dans trunk/default, mais est ajouté à une branche release.

1 votes

@CraigMcQueen merci pour cette excellente réponse, elle m'a beaucoup aidé ! Je me demande cependant si le comportement souhaité est d'utiliser Cython lorsqu'il est disponible ? Il me semble qu'il serait préférable d'utiliser par défaut les fichiers c pré-générés, à moins que l'utilisateur ne veuille explicitement utiliser Cython, auquel cas il peut définir la variable d'environnement ou autre. Cela rendrait l'installation plus stable/robuste, car l'utilisateur peut obtenir des résultats différents selon la version de Cython qu'il a installée - il peut même ne pas être conscient qu'il l'a installée et que cela affecte la construction du paquet.

21voto

kynan Points 2334

En complément de la réponse de Craig McQueen : voir ci-dessous pour savoir comment remplacer l'option sdist pour que Cython compile automatiquement vos fichiers sources avant de créer une distribution des sources.

De cette façon, vous ne courez pas le risque de distribuer accidentellement des données périmées. C sources. Il est également utile 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 de l'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

21voto

Colonel Panic Points 18390

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 de disposer de Cython.

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 n'a probablement pas envie de l'utiliser uniquement pour installer votre module. De plus, la version qu'il possède n'est peut-être pas la même que celle que vous avez utilisée, et peut ne pas compiler vos sources correctement.

Cela signifie simplement que le fichier setup.py que vous livrez 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"])]
)

7voto

Lennart Regebro Points 52510

Le plus simple est d'inclure les deux mais de n'utiliser que 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 veulent recompiler le fichier .pyx peuvent installer Pyrex et le faire manuellement.

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

Ce que la documentation ne dit pas, c'est comment rendre cette condition, mais

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

Il devrait s'en occuper.

1 votes

Merci pour votre réponse. C'est raisonnable, bien que je préfère que l'option setup.py peut construire directement à partir du .pyx lorsque Cython est installé. Ma réponse a également mis en œuvre cette mesure.

0 votes

Eh bien, c'est tout le sens de ma réponse. Ce n'était juste pas un setup.py complet.

1voto

zzart Points 2162

Il s'agit d'un setup script que j'ai écrit et qui permet d'inclure plus facilement des répertoires imbriqués à l'intérieur du build. On a besoin de l'exécuter à partir d'un dossier dans un paquet.

La structure de Givig ressemble à ça :

__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 ;)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X