4. Construyendo Extensiones C y C++

Una extensión C para CPython es una biblioteca compartida (por ejemplo, un archivo .so en Linux, .pyd en Windows), que exporta una función de inicialización.

Para que sea importable, la biblioteca compartida debe estar disponible en PYTHONPATH, y debe tener el nombre del módulo, con una extensión adecuada. Cuando se usan distutils, el nombre de archivo correcto se genera automáticamente.

La función de inicialización tiene la firma:

PyObject* PyInit_modulename(void)

Retorna un módulo completamente inicializado o una instancia PyModuleDef. Ver Inicializando módulos en C para más detalles.

Para los módulos con nombres solo ASCII, la función debe llamarse PyInit_<modulename>, con <modulename> reemplazado por el nombre del módulo. Cuando se usa Inicialización multifase, se permiten nombres de módulos que no sean ASCII. En este caso, el nombre de la función de inicialización es PyInitU_<modulename>, con <modulename> codificado usando la codificación punycode de Python con guiones reemplazados por guiones bajos. En Python:

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

Es posible exportar múltiples módulos desde una única biblioteca compartida definiendo múltiples funciones de inicialización. Sin embargo, importarlos requiere el uso de enlaces simbólicos o un importador personalizado, porque de forma predeterminada solo se encuentra la función correspondiente al nombre del archivo. Consulte la sección «Múltiples módulos en una biblioteca» en PEP 489 para más detalles.

4.1. Construyendo Extensiones C y C++ con distutils

Los módulos de extensión se pueden construir utilizando distutils, que se incluye en Python. Dado que distutils también admite la creación de paquetes binarios, los usuarios no necesitan necesariamente un compilador y distutils para instalar la extensión.

Un paquete distutils contiene un script de controlador, setup.py. Este es un archivo Python simple, que, en el caso más simple, podría verse así:

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

Con esto setup.py, y un archivo demo.c, ejecutando:

python setup.py build

compilará demo.c, y producirá un módulo de extensión llamado demo en el directorio build. Dependiendo del sistema, el archivo del módulo terminará en un subdirectorio build/lib.system, y puede tener un nombre como demo.so o demo.pyd.

En setup.py, toda la ejecución se realiza llamando a la función setup. Esto toma un número variable de argumentos de palabras clave, de los cuales el ejemplo anterior usa solo un subconjunto. Específicamente, el ejemplo especifica metainformación para construir paquetes, y especifica el contenido del paquete. Normalmente, un paquete contendrá módulos adicionales, como módulos fuente Python, documentación, subpaquetes, etc. Consulte la documentación de distutils en Distribución de módulos de Python (versión heredada) para obtener más información sobre las características de distutils; Esta sección explica la construcción de módulos de extensión solamente.

Es común precalcular argumentos para setup(), para estructurar mejor el script del controlador. En el ejemplo anterior, el argumento ext_modules para setup() es una lista de módulos de extensión, cada uno de los cuales es una instancia de Extension . En el ejemplo, la instancia define una extensión llamada demo que se construye compilando un solo archivo fuente demo.c.

En muchos casos, construir una extensión es más complejo, ya que es posible que se necesiten preprocesadores adicionales y bibliotecas. Esto se demuestra en el siguiente ejemplo.

from distutils.core import setup, Extension

module1 = Extension('demo',
                    define_macros = [('MAJOR_VERSION', '1'),
                                     ('MINOR_VERSION', '0')],
                    include_dirs = ['/usr/local/include'],
                    libraries = ['tcl83'],
                    library_dirs = ['/usr/local/lib'],
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       author = 'Martin v. Loewis',
       author_email = 'martin@v.loewis.de',
       url = 'https://docs.python.org/extending/building',
       long_description = '''
This is really just a demo package.
''',
       ext_modules = [module1])

En este ejemplo, se llama a setup() con metainformación adicional, que se recomienda cuando se deben construir paquetes de distribución. Para la extensión en sí, especifica las definiciones de preprocesador, incluye directorios, directorios de biblioteca y bibliotecas. Dependiendo del compilador, distutils pasa esta información de diferentes maneras al compilador. Por ejemplo, en Unix, esto puede resultar en los comandos de compilación:

gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 -c demo.c -o build/temp.linux-i686-2.2/demo.o

gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 -o build/lib.linux-i686-2.2/demo.so

Estas líneas son solo para fines de demostración; Los usuarios de distutils deben confiar en que distutils obtiene las invocaciones correctas.

4.2. Distribuyendo sus módulos de extensión

Cuando una extensión se ha creado correctamente, hay tres formas de usarla.

Los usuarios finales generalmente querrán instalar el módulo, lo hacen ejecutando:

python setup.py install

Los mantenedores de módulos deben producir paquetes fuente; para hacerlo, ejecutan:

python setup.py sdist

En algunos casos, se deben incluir archivos adicionales en una distribución de origen; esto se hace a través de un archivo MANIFEST.in; ver Especificar los archivos a distribuir para más detalles.

Si la distribución de origen se ha creado correctamente, los encargados del mantenimiento también pueden crear distribuciones binarias. Dependiendo de la plataforma, se puede usar uno de los siguientes comandos para hacerlo.:

python setup.py bdist_wininst
python setup.py bdist_rpm
python setup.py bdist_dumb