2012-06-13 8 views
33

Estoy tratando de tomar algunas funciones de una gran biblioteca compartida de C++ (libbig.so) y exponerlas a Python a través de Cython. Para hacerlo, tengo un pequeño archivo C++ (small.cpp) que proporciona una capa delgada alrededor de la funcionalidad de la biblioteca compartida que necesito, de una manera que hace que sea fácil llamar a través de Cython (pysmall.pyx).Distribuir una biblioteca compartida y un código C con un módulo de extensión Cython

libbig.so -> small.cpp, small.h -> libsmall.so -> pysmall.pyx -> pysmall.cpp -> pysmall.so

puedo construir y ejecutar este módulo de extensión en mi propia computadora: simplemente compilo small.cpp en libsmall.so, y luego digo "libraries = ['small']" en el objeto Extension en setup.py para construir el módulo de extensión pysmall.so.

Ahora estoy tratando de distribuir este módulo de extensión, y estoy teniendo dificultades para rastrear los recursos que describen las mejores prácticas de setup.py para distribuir un módulo de Cython, así como fuentes C y bibliotecas compartidas. He leído "Installing Python Modules", "Distributing Python Modules" y "Distributing Cython Modules". Entiendo cómo distribuir un módulo de extensión por sí mismo. Estoy menos seguro de la mejor forma de distribuir las dependencias del módulo de extensión.

La documentación de Cython indica que debe incluir los archivos .cpp generados, así como los archivos .pyx, en caso de que Cython no esté presente, pero no proporciona el código para demostrar cómo manejar mejor cada situación. Tampoco menciona cómo distribuir las bibliotecas compartidas de las que depende el módulo Cython.

Estoy explorando los scripts setup.py de pandas, lxml, pyzmq, h5py, y más, y hay un poco de trabajo extraño sucediendo. Si alguien tiene punteros o código de ejemplo que podría acelerar este proceso, ciertamente lo agradecería.

Respuesta

16

1) Distribuir libbig.so

Este es un problema que pitón no va a ayudarle con. ¿A quién estás apuntando? Si es Linux, ¿puede solicitar que lo instalen con su administrador de paquetes? Si libbig no se distribuye a través de un gestor de paquetes o no es Linux y está apuntando a varias arquitecturas, puede que tenga que distribuir la fuente libbig.

2) Cython/setuptools.

Francamente, creo que es más fácil simplemente exigir que las personas tengan Cython. De esta forma, solo hay una versión de verdad del código, y no tiene que preocuparse por las incoherencias entre el código .pyx y .cpp. La forma más fácil de hacerlo es usar setuptools en lugar de distutils. De esta manera, se puede utilizar:

setup('mypackage', 
    ... 
    install_requires=['cython']) 

En total, la secuencia de comandos setup.py se verá algo como:

# setup.py 

from setuptools import setup, Extension 
from Cython.Distutils import build_ext 

pysmall = Extension('pysmall', 
    sources = ['pysmall.pyx', 'small.cpp'], 
    include_dirs = ['include/']) 

setup(name='mypackage', 
     packages=['yourpurepythonpackage'], 
     install_requires=['cython==0.17'], 
     ext_modules=[pysmall], 
     cmdclass = {'build_ext': build_ext}) 

Si no te gusta la idea de exigir Cython, usted podría hacer algo como:

# setup.py 

import warnings 
try: 
    from Cython.Distutils import build_ext 
    from setuptools import setup, Extension 
    HAVE_CYTHON = True 
except ImportError as e: 
    HAVE_CYTHON = False 
    warnings.warn(e.message) 
    from distutils.core import setup, Extension 
    from distutils.command import build_ext 

pysmall = Extension('pysmall', 
    sources = ['pysmall.pyx', 'small.cpp'], 
    include_dirs = ['include/']) 

configuration = {'name': 'mypackage', 
     'packages': ['yourpurepythonpackage'], 
     'install_requires': ['cython==0.17'], 
     'ext_modules': [pysmall], 
     'cmdclass': {'build_ext': build_ext}} 

if not HAVE_CYTHON: 
    pysmall.sources[0] = 'pysmall.cpp' 
    configuration.pop('install_requires') 

setup(**configuration) 
+2

Tenga en cuenta que en las versiones más recientes de 'setuptools' y' distutils' (estoy usando 'setuptools' 5.7), los comandos se movieron a sus propios módulos. Entonces, querría hacer 'desde setuptools.command.build_ext import build_ext' o desde' distutils' respectivamente. – Midnighter

+1

Su primer setup.py es que importa Cython.Distutils antes de que tenga la oportunidad de instalarlo si no está presente. – zneak

+0

Otra opción sería crear un paquete conda, que podría agruparse 'libbig.so'. https://conda.io/docs/user-guide/tutorials/build-postgis.html – oLas

7

Aquí está mi solución difícil. La idea es "ocultar" la presencia de cython hasta que los requisitos lo instalen. Esto se puede lograr mediante una evaluación perezosa.He aquí un ejemplo:

from setuptools import setup, Extension 

class lazy_cythonize(list): 
    def __init__(self, callback): 
     self._list, self.callback = None, callback 
    def c_list(self): 
     if self._list is None: self._list = self.callback() 
     return self._list 
    def __iter__(self): 
     for e in self.c_list(): yield e 
    def __getitem__(self, ii): return self.c_list()[ii] 
    def __len__(self): return len(self.c_list()) 

def extensions(): 
    from Cython.Build import cythonize 
    ext = Extension('native_ext_name', ['your/src/*.pyx']) 
    return cythonize([ext]) 


configuration = { 
    'name': 'mypackage', 
    'packages': ['yourpurepythonpackage'], 
    'install_requires': ['cython==0.17'], 
    'ext_modules': lazy_cythonize(extensions) 
} 

setup(**configuration) 

lazy_cythonize es una lista falsa que genera sus elementos internos sólo cuando alguien trata de acceder a ella.
Cuando es necesario, esta clase importa Cython.Build y genera la lista de extensiones. Esto evita guardar los archivos *.c en su proyecto, lo que requiere que Cython se instale cuando el módulo está en construcción.

Bastante complicado, pero en realidad está funcionando.

+2

patrón genial, esto también funciona con setuptools que requieren cython durante el tiempo de ejecución de configuración con setup_requires :) – marscher

+0

Esto es exactamente lo que estaba buscando. Sentí la necesidad de instalar Cython, cuando Cython se importó en la parte superior del script. – csl

5

He presionado una solución para setuptools 288, programada para su lanzamiento como setuptools 18.0. This changelog entry describe una técnica que debería funcionar con esa compilación. Un beta release está disponible para probar.

+0

¿Qué sucede si un usuario tiene una versión anterior de setuptools? –

+0

@RichardHansen Tienes un par de opciones. Puede exigir a los usuarios que actualicen (probablemente sea demasiado pronto para eso), agrupe ez_setup.py y fuerce la actualización de las herramientas de configuración durante la instalación de su paquete (no recomendado), o detecte la versión de instalación de herramientas y repliegue a un enfoque menos elegante. –

+0

todavía tiene que usar la solución con el patrón de lista perezosa en setuptools> = 18 para setup_requires deps (! = Cython) @ JasonR.Coombs – marscher

Cuestiones relacionadas