Cómo debería estructurar un paquete Python que contiene código Cython


Me gustaría hacer un paquete Python que contenga algo de código Cython. Tengo el código Cython funcionando bien. Sin embargo, ahora quiero saber la mejor manera de empaquetarlo.

Para la mayoría de las personas que solo quieren instalar el paquete, me gustaría incluir el archivo .c que crea Cython, y hacer arreglos para que setup.py lo compile para producir el módulo. Entonces el usuario no necesita Cython instalado para instalar el paquete.

Pero para las personas que pueden querer modificar el paquete, También me gustaría proporcionar los archivos Cython .pyx, y de alguna manera también permitir setup.py para construirlos usando Cython (por lo que los usuarios necesitan Cython instalado).

¿Cómo debo estructurar los archivos en el paquete para atender ambos escenarios?

La documentación de Cython da una pequeña guía. Pero no dice cómo hacer un único setup.py que maneja tanto los casos con/sin Cython.

Author: Mike Pennington, 2010-12-22

9 answers

He hecho esto yo mismo ahora, en un paquete Python simplerandom (BitBucket repo - EDIT: now github) (No espero que este sea un paquete popular, pero fue una buena oportunidad para aprender Cython).

Este método se basa en el hecho de que construir un archivo .pyx con Cython.Distutils.build_ext (al menos con la versión 0.14 de Cython) siempre parece crear un archivo .c en el mismo directorio que el archivo fuente .pyx.

Aquí hay una versión reducida de setup.py que espero muestre la elementos esenciales:

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

También edité MANIFEST.in para asegurar que mycythonmodule.c está incluido en una distribución de origen (una distribución de origen que se crea con python setup.py sdist):

...
recursive-include cython *
...

No confirmo mycythonmodule.c al control de versiones 'trunk' (o 'default' para Mercurial). Cuando hago una versión, necesito recordar hacer un python setup.py build_ext primero, para asegurarme de que mycythonmodule.c está presente y actualizado para la distribución del código fuente. También hago una rama release, y confirmo el archivo C en la rama. De esa manera tengo un registro histórico del archivo C que se distribuyó con esa versión.

 58
Author: Craig McQueen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-17 11:32:47

Añadiendo a la respuesta de Craig McQueen: vea a continuación cómo anular el comando sdist para que Cython compile automáticamente sus archivos fuente antes de crear una distribución fuente.

De esa manera no corre ningún riesgo de distribuir accidentalmente fuentes C obsoletas. También ayuda en el caso de que tenga un control limitado sobre el proceso de distribución, por ejemplo, al crear automáticamente distribuciones a partir de la integración continua, 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
 17
Author: kynan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-24 12:24:18

Http://docs.cython.org/src/reference/compilation.html#distributing-cython-modules

Se recomienda encarecidamente que distribuya el generado .c, así como sus fuentes de Cython, para que los usuarios puedan instalar su módulo sin necesidad de tener Cython disponible.

También se recomienda que la compilación de Cython no esté habilitada por defecto en la versión que distribuya. Incluso si el usuario tiene Cython instalado, probablemente no quiera usarlo solo para instalar su módulo. Además, la versión que tiene puede no ser la misma que usaste, y puede no compilar tus fuentes correctamente.

Esto simplemente significa que el setup.py el archivo con el que se envía solo será un archivo distutils normal en el generado .archivos c, para el ejemplo básico tendríamos en su lugar:

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

setup(
    ext_modules = [Extension("example", ["example.c"])]
)
 15
Author: Colonel Panic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-06 11:50:45

¿Lo más fácil es incluir ambos, pero solo usar el archivo c? Incluyendo el .el archivo pyx es agradable, pero no es necesario una vez que tenga el .archivo c de todos modos. La gente que quiere recompilar el .pyx puede instalar Pyrex y hacerlo manualmente.

De lo contrario, debe tener un comando build_ext personalizado para distutils que primero compile el archivo C. Cython ya incluye uno. http://docs.cython.org/src/userguide/source_files_and_compilation.html

Lo que esa documentación no hace es decir cómo hacer esto condicional, pero

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

Debería manejarlo.

 6
Author: Lennart Regebro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-22 10:18:06

Incluyendo (Cython) generado .los archivos c son bastante raros. Especialmente cuando incluimos eso en git. Prefiero usar setuptools_cython. Cuando Cython no está disponible, construirá un egg que tiene un entorno Cython incorporado, y luego construirá su código usando el egg.

Un posible ejemplo: https://github.com/douban/greenify/blob/master/setup.py


Actualización (2017-01-05):

Desde setuptools 18.0, no hay necesidad de usar setuptools_cython. Aquí es un ejemplo para construir Cython project desde cero sin setuptools_cython.

 4
Author: McKelvin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-05 09:06:41

Este es un script de instalación que escribí que hace que sea más fácil incluir directorios anidados dentro de la compilación. Uno necesita ejecutarlo desde una carpeta dentro de un paquete.

Givig estructura como esta:

__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

Feliz compilación;)

 2
Author: zzart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-24 13:33:11

El simple truco que se me ocurrió:

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

Simplemente instale Cython si no se pudo importar. Uno probablemente no debería compartir este código, pero para mis propias dependencias es lo suficientemente bueno.

 2
Author: kay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-18 01:01:17

La forma más fácil que encontré usando solo setuptools en lugar de la característica distutils limitada es

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
Author: MosteM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-29 12:38:17

Todas las demás respuestas dependen de

  • distutils
  • importando desde Cython.Build, lo que crea un problema de gallina y huevo entre requerir cython a través de setup_requires e importarlo.

Una solución moderna es setuptools en su lugar, ver esta respuesta (el manejo automático de extensiones de Cython requiere setuptools 18.0, es decir, está disponible desde hace muchos años). Un estándar moderno setup.py con manejo de requisitos, un punto de entrada y un módulo cython podría parecer así:

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'],
        ),
    ],
)
 0
Author: bluenote10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-30 11:29:46