Extiende Python usando C o C++

Si sabe programar en C, agregar nuevos módulos integrados a Python es muy fácil. Estos módulos de extensión pueden hacer dos cosas que no se pueden hacer directamente en Python: pueden implementar nuevos tipos de objetos integrados y pueden llamar a funciones de la biblioteca C y llamadas al sistema.

Para admitir extensiones, la API de Python (interfaz de programador de aplicaciones) define un conjunto de funciones, macros y variables que brindan acceso a la mayoría de los aspectos del sistema de ejecución de Python. La API de Python se incorpora a los archivos fuente de C al incluir archivos de encabezado "Python.h".

La compilación de módulos de extensión depende de su uso previsto y de la configuración de su sistema; los detalles se proporcionan en capítulos posteriores.

notas

 

La interfaz de extensión C es específica de CPython y los módulos de extensión no están disponibles para otras implementaciones de Python. En muchos casos, es posible evitar escribir extensiones de C y preservar la portabilidad a otras implementaciones. Por ejemplo, si su caso de uso es llamar a funciones de la biblioteca C o llamadas al sistema, debería considerar usar el módulo ctypes o la biblioteca cffi en lugar de escribir código C personalizado. Estos módulos le permiten escribir código Python para interactuar con código C y son más portátiles entre implementaciones de Python que escribir y compilar módulos de extensión C.

1.1 Un ejemplo sencillo

Creemos un spammódulo de extensión llamado (Comida favorita de los fanáticos de Monty Python...), suponiendo que queremos crear una interfaz de Python para la función system() 1 de la biblioteca C. Esta función toma una cadena terminada en nulo como argumento y devuelve un número entero. Queremos que esta función se pueda llamar desde Python de esta manera:

>>> importar  spam 
>>> estado = spam . sistema( "ls -l" )

Primero cree un archivo spammodule.c. (Históricamente, si se llamaba a un módulo spam, se llamaba al archivo C que contenía su implementación  spammodule.c; si el nombre del módulo era largo, por ejemplo spammify, el nombre del módulo podía ser simplemente spammify.c.)

La primera línea de nuestro archivo podría ser:

#incluir  <Python.h>

Presenta la API de Python (si lo desea, puede agregar comentarios y avisos de derechos de autor que describan el propósito del módulo).

notas

 

Debido a que Python puede definir algunas definiciones de preprocesador que afectan los encabezados estándar en algunos sistemas, debe incluirPython.h estas definiciones antes de incluir cualquier encabezado estándar.

Además de los símbolos definidos en archivos de encabezado estándar, todos los símbolos visibles para el usuario definidos por Python.htienen el prefijo Pyo . PYPor conveniencia, y debido a que el intérprete de Python hace un uso extensivo de ellos, "Python.h" se incluyen algunos encabezados estándar: <stdio.h>, <string.h><errno.h>y <stdlib.h>. Si este último encabezado no existe en su sistema, declarará directamente las funciones malloc(), free()realloc().

Lo siguiente que agregamos al archivo del módulo es la función C que será llamada cuando se evalúe la expresión de Python spam.system(string)(veremos en breve cómo termina siendo llamada):

PyObject estático * 
spam_system (PyObject * self, PyObject * args)
{ const char * comando;
    puntos internos ; if ( ! PyArg_ParseTuple(args, "s" , & comando))
         devuelve NULL ; 
    sts = sistema(comando);
    devolver PyLong_FromLong(pts); 
}
      

     

Existe una conversión simple de una lista de parámetros en Python (por ejemplo, una sola expresión) a los parámetros pasados ​​a una función C. Las funciones C siempre tienen dos parámetros, generalmente llamados self  y args ."ls -l"

El parámetro self apunta al objeto del módulo de la función a nivel de módulo; para los métodos, apuntará a la instancia del objeto.

El argumento args será un puntero a un objeto tupla de Python que contiene los argumentos. Cada elemento de la tupla corresponde a un argumento en la lista de argumentos de llamada. Los parámetros son objetos de Python; para poder hacer algo con ellos en una función de C, tenemos que convertirlos a valores de C. La función PyArg_ParseTuple() en la API de Python  verifica el tipo de argumento y lo convierte a un valor C. Utiliza una cadena de plantilla para determinar los tipos de parámetros requeridos y el tipo de variable C utilizada para almacenar el valor convertido. Más sobre esto más adelante.

PyArg_ParseTuple() devuelve verdadero (distinto de cero) si todos los argumentos tienen el tipo correcto y sus componentes se han almacenado en las variables en las direcciones pasadas. Si se pasa una lista de argumentos no válida, devolverá falso (cero). En el último caso, también genera la excepción apropiada, por lo que la función que llama puede devolver inmediatamente NULL (como vimos en el ejemplo).

1.2 Intermezzo: errores y excepciones

Una convención importante en todo el intérprete de Python es la siguiente: cuando una función falla, debe establecer una condición de excepción y devolver un valor de error (generalmente un puntero NULL ). Las excepciones se almacenan en una variable global estática dentro del intérprete; si esta variable es NULL, no se producirá ninguna excepción. La segunda variable global almacena el "valor asociado" de la excepción (el segundo argumento para generar ). La tercera variable contiene el rastreo de la pila en caso de que el error se origine en el código Python. Estas tres variables son el equivalente en C del resultado en Python (consulte la sección sobre el módulo sys.exc_info() en la Referencia de la biblioteca de Python ). sys Es importante conocerlos para comprender cómo se entregan los errores.

La API de Python define muchas funciones para establecer varios tipos de excepciones.

El más común es PyErr_SetString() , cuyos parámetros son un objeto de excepción y una cadena C. Los objetos de excepción suelen ser objetos predefinidos, por ejemplo,  PyExc_ZeroDivisionErroruna cadena C que indica la causa del error, convertida en un objeto de cadena de Python y almacenada como el "valor asociado" de la excepción.

Otra función útil es PyErr_SetFromErrno() , que solo acepta parámetros de excepción y construye el valor asociado verificando variables globales errno. La función más general es  PyErr_SetObject() , que acepta dos parámetros de objeto: la excepción y su valor asociado. No necesita que el objeto Py_INCREF() se pase a ninguna de estas funciones.

Puede probar de forma no destructiva si se ha establecido una excepción con  PyErr_Occurred() . Esto devuelve el objeto de excepción actual, o NULL si no ocurrió ninguna excepción. Por lo general, no necesita llamar a  PyErr_Occurred() para ver si ocurrió un error en una llamada de función, ya que debería poder saberlo por el valor de retorno.

Cuando una función f que llama a otra función g detecta que esta última falló, la propia f debería devolver un valor de error (generalmente NULL o ). No debería llamar a una de las funciones; una de las funciones ya ha sido llamada por g . La persona que llama a f también debería devolver una indicación de error a su persona que llama , nuevamente sin llamar -1PyErr_*()PyErr_*(), etc. La causa más detallada del error ya ha sido informada por la función que detectó el error por primera vez. Una vez que un error llega al bucle principal del intérprete de Python, el código Python que se está ejecutando actualmente se cancela y se intenta encontrar un controlador de excepciones especificado por el programador de Python.

(En algunos casos, un módulo puede PyErr_*()dar un mensaje de error más detallado llamando a otra función, en cuyo caso está bien hacerlo. Sin embargo, como regla general, esto no es necesario y puede El número de errores que se producirían provocar que se pierda información sobre la causa: la mayoría de las operaciones pueden fallar por diversas razones).

Para ignorar una excepción establecida por una llamada de función fallida, la condición de excepción debe borrarse explícitamente llamando a PyErr_Clear() . La única vez que el código C debería llamar a PyErr_Clear() es si no quiere pasar el error al intérprete, pero quiere manejarlo completamente por sí solo (tal vez probando otros métodos o fingiendo que nada salió mal).

Cada malloc()llamada fallida debe convertirse en una excepción  malloc()(o realloc()) la persona que llama directamente debe llamar a  PyErr_NoMemory() y devolver el indicador de falla. Todas las funciones de creación de objetos (por ejemplo, PyLong_FromLong() ) ya hacen esto, por lo que esta nota solo es malloc()relevante para funciones llamadas directamente.

También tenga en cuenta que, con excepciones importantes como y, las funciones que devuelven un estado entero generalmente devuelven un valor positivo o cero en caso de éxito o fracaso , al igual que la llamada al sistema Unix PyArg_ParseTuple() .-1

Finalmente, cuando devuelva un indicador de error, tenga cuidado al limpiar la basura ( objetos creados mediante la creación de llamadas Py_XDECREF()Py_DECREF() ).

La elección de qué excepción lanzar depende totalmente de usted. Todas las excepciones integradas de Python tienen correspondientes objetos C predeclarados que  PyExc_ZeroDivisionErrorpuedes usar directamente, por ejemplo. Por supuesto, debe elegir su excepción sabiamente; no la use PyExc_TypeErrorpara indicar que el archivo no se puede abrir (lo cual probablemente debería ser así PyExc_IOError). La  función PyArg_ParseTuple() generalmente se activa si hay un problema con la lista de argumentos PyExc_TypeError. PyExc_ValueErrorEsto se aplica si tiene un parámetro cuyo valor debe estar dentro de un rango específico o debe cumplir otras condiciones  .

También puede definir nuevas excepciones que sean exclusivas de su módulo. Para hacer esto, normalmente declaras una variable de objeto estática al principio del archivo:

PyObject estático * SpamError;

PyInit_spam()inicialícelo usando el objeto de excepción en la función de inicialización del módulo () (ignorando la verificación de errores por ahora):

PyMODINIT_FUNC
 PyInit_spam ( vacío ) 
{ 
    PyObject * m; 

    m = PyModule_Create( & módulo de spam);
    si (m ==  NULL )
         devuelve  NULL ; 

    SpamError = PyErr_NewException( "spam.error" , NULL , NULL ); 
    Py_INCREF(SpamError); 
    PyModule_AddObject(m, "error" , SpamError);
    devolver m; 
}

Tenga en cuenta que el nombre Python del objeto de excepción es spam.error. La  función PyErr_NewException() crea una clase cuya clase base es Exception (a menos que se pase otra clase en lugar de NULL ), como se describe en Excepciones integradas .

También tenga en cuenta que esta SpamErrorvariable contiene una referencia a la clase de excepción recién creada; ¡esto es intencional! Dado que las excepciones se pueden eliminar del módulo mediante código externo, se necesita una referencia propiedad de la clase para garantizar que no se descarte, lo que resultará en un SpamErrorpuntero colgante. Si se convierte en un puntero colgante, el código C que genera la excepción puede provocar un volcado de memoria u otros efectos secundarios inesperados.

PyMODINIT_FUNCAnalizaremos su uso como tipo de retorno de función más adelante en este ejemplo.

spam.errorSe pueden generar excepciones en un módulo de extensión usando una llamada como esta : PyErr_SetString()

PyObject estático * 
spam_system (PyObject * self, PyObject * args)
{ const char * comando;
    puntos internos ; if ( ! PyArg_ParseTuple(args, "s" , & comando))
         devuelve NULL ; 
    sts = sistema(comando);
    if (sts < 0 ) {
        PyErr_SetString(SpamError, "Error del comando del sistema" );
        devolver NULO ; 
    } regresar
      

       
    PyLong_FromLong(pts); 
}

1.3 Volver al ejemplo

Volviendo a nuestra función de ejemplo, ahora debería poder comprender la siguiente afirmación:

if ( ! PyArg_ParseTuple(args, "s" , & comando))
     devuelve  NULL ;

Si se detecta un error en la lista de parámetros, devuelve NULL (el indicador de error para funciones que devuelven punteros de objeto), dependiendo de  PyArg_ParseTuple() . De lo contrario, el valor de cadena del parámetro se ha copiado en una variable local command. Esta es una asignación de puntero y no debe modificar la cadena a la que apunta (por lo que en el estándar C la variable commanddebe declararse correctamente como).const char *command

La siguiente declaración es una llamada a la función Unix PyArg_ParseTuple()system() , pasándole la cadena que acabamos de obtener :

sts = sistema(comando);

El valor que nuestra función debe spam.system()devolver en forma de objeto Python. stsEsto se hace usando la función PyLong_FromLong() .

devolver  PyLong_FromLong (pts);

En este caso devolverá un objeto entero. (Sí, en Python, ¡incluso los números enteros son objetos en el montón!)

Si su función C no devuelve argumentos útiles (funciones devueltas  void), la función Python correspondiente debe devolver None. Necesitas este modismo para hacer esto (implementado por la macro Py_RETURN_NONE  ):

Py_INCREF(Py_Ninguno);
devolver Py_None;

Py_None es el nombre C de un objeto especial de Python None. Es un objeto Python real, no un puntero NULL y, como hemos visto, un puntero NULL significa "error" en la mayoría de los casos.

1.4 Tabla de métodos del módulo y función de inicialización

Prometí mostrar cómo spam_system()llamarlo desde un programa Python. Primero, debemos incluir su nombre y dirección en la "Tabla de métodos":

static PyMethodDef SpamMethods[] = {
    ...
    { "system" , spam_system, METH_VARARGS,
      "Ejecutar un comando de shell". },
    ...
    { NULL , NULL , 0 , NULL }         /* Centinela */ 
};
        
        
        
        

Tenga en cuenta la tercera entrada (  METH_VARARGS). Esta es una bandera que le dice al intérprete la convención de llamada que debe usar para la función C. Siempre debe ser METH_VARARGSo; el valor indica el uso de una variante obsoleta. METH_VARARGS | METH_KEYWORDS0PyArg_ParseTuple()

Cuando se usan únicamente , las funciones deben esperar que los argumentos a nivel de Python se pasen a PyArg_ParseTuple()METH_VARARGS como tuplas aceptables para el análisis  ; a continuación se proporciona más información sobre esta característica.

METH_KEYWORDS Este bit se puede establecer en el tercer campo si se deben pasar argumentos de palabras clave a la función. En este caso, la función C debería aceptar un tercer parámetro, que será un diccionario de palabras clave. Se utiliza para analizar los parámetros de dichas funciones. PyObject *PyArg_ParseTupleAndKeywords()

Se debe hacer referencia a la tabla de métodos en la estructura de definición del módulo:

 struct static PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT, "spam" ,    /* nombre del módulo */ 
    spam_doc, /* documentación del módulo, puede ser NULL */ - 1 ,        /* tamaño del estado por intérprete del módulo,  o -1 si el módulo mantiene el estado en variables globales. */ 
    Métodos de spam
};
    
    

Esta estructura, a su vez, debe pasarse al intérprete en la función de inicialización del módulo. La función de inicialización debe tener nombre  PyInit_name(), donde nombre es el nombre del módulo y debe ser staticel único elemento no definido en el archivo del módulo:

PyMODINIT_FUNC
 PyInit_spam ( void ) 
{ return PyModule_Create( & spammodule); 
}
    

Tenga en cuenta que PyMODINIT_FUNC declara la función como un tipo de retorno, declara cualquier declaración de enlace especial requerida por la plataforma y, para C++, declara la función como .PyObject *extern "C"

Se llama cuando un programa Python spamimporta un módulo por primera vez  . (Consulte la nota a continuación sobre la incorporación de Python). Llama, devuelve un objeto de módulo e inserta el objeto de función incorporada en el módulo recién creado de acuerdo con la tabla PyModule_Create()PyInit_spam() (matriz de estructuras) que se encuentra en la definición del módulo. Devuelve un puntero al objeto de módulo que creó. Para algunos errores, puede abortar con un error fatal o devolver NULL si el módulo no se puede inicializar satisfactoriamente. La función init debe devolver el objeto del módulo a su llamador para poder insertarlo en PyMethodDef PyModule_Create()sys.modules

Al incrustar Python, PyInit_spam()esta función no se llama automáticamente a menos que exista una entrada en la tabla PyImport_Inittab. Para agregar un módulo a la tabla de inicialización, use PyImport_AppendInittab() , seguido opcionalmente de las importaciones del módulo:

int 
main ( int argc, char  * argv[]) 
{ wchar_t * programa = Py_DecodeLocale(argv[ 0 ], NULL );
    if (programa == NULL ) { 
        fprintf(stderr, "Error grave: no se puede decodificar argv[0] \n " ); 
        salir( 1 ); 
    } /* Agregar un módulo integrado, antes de Py_Initialize */ 
    PyImport_AppendInittab( "spam" , PyInit_spam); /* Pasa argv[0] al intérprete de Python */
      

    

    
    Py_SetProgramName(programa); /* Inicializa el intérprete de Python. Requerido. */ 
    Py_Initialize(); /* Opcionalmente importar el módulo; alternativamente,  la importación se puede aplazar hasta que el script incorporado  la importe. */ 
    PyImport_ImportModule( "spam" ); 
    ... 
    PyMem_RawFree(programa); devolver 0 ; 
}

    

    




     

notas

 

sys.modulesEliminar entradas de múltiples intérpretes dentro de un proceso o importar un módulo compilado en múltiples intérpretes (o fork()ejecutar sin intervención exec()) puede causar problemas para algunos módulos de extensión. Los autores de módulos de extensión deben tener cuidado al inicializar estructuras de datos internas.

Se incluye un módulo de ejemplo más importante en la distribución fuente de Python, llamado Modules/xxmodule.c. Este archivo puede usarse como plantilla o simplemente leerse como ejemplo.

notas

 

A diferencia de nuestro spamejemplo, xxmoduleutiliza la inicialización de múltiples fases  (nueva en Python 3.5), que devuelve una estructura PyModuleDef  PyInit_spamy deja la creación del módulo al mecanismo de importación. Consulte PEP 489 para obtener detalles sobre la inicialización multifase .

1.5 Compilación y vinculación

Antes de poder usar su nueva extensión, necesita hacer dos cosas más: compilarla y vincularla con su sistema Python. Si utiliza la carga dinámica, los detalles pueden depender del estilo de carga dinámica utilizado por su sistema; para obtener más información, consulte el capítulo sobre creación de módulos de extensión (capítulo Creación de extensiones C y C++) y la información adicional relacionada únicamente con la creación en Windows . Información (  capítulo Creación de extensiones C y C++ en Windows).

Si no puede utilizar la carga dinámica, o si desea que su módulo sea una parte permanente del intérprete de Python, debe cambiar los ajustes de configuración y reconstruir el intérprete. Afortunadamente, esto es muy fácil en Unix: simplemente coloque su archivo ( spammodule.cpor ejemplo) Modules/en el directorio de la distribución fuente descomprimida, agregue una línea  Modules/Setup.localque describa su archivo:

spam spammodule.o

y reconstruir el intérprete ejecutando make en el directorio superior . También puedes ejecutar make en un subdirectorio Modules/ , pero primero debes  reconstruir allí Makefileejecutando "  make Makefile". (Debe hacer esto cada vez que cambie el archivo  Setup).

Si su módulo requiere vinculación con otras bibliotecas, estas bibliotecas también se pueden enumerar en líneas en el archivo de configuración, por ejemplo:

spam spammodule.o -lX11

1.6 Llamar a funciones de Python desde C

Hasta ahora, nos hemos centrado en hacer que las funciones C sean invocables desde Python. También es útil a la inversa: llamar a funciones de Python desde C. Esto es especialmente cierto para las bibliotecas que admiten las llamadas funciones de "devolución de llamada". Si la interfaz C usa devoluciones de llamada, el equivalente de Python generalmente necesita proporcionar al programador de Python un mecanismo de devolución de llamada; la implementación necesita llamar a la función de devolución de llamada de Python desde la devolución de llamada de C. También son concebibles otros usos.

Afortunadamente, el intérprete de Python es fácil de llamar de forma recursiva y tiene una interfaz estándar para llamar a funciones de Python. (No entraré en detalles sobre cómo llamar al analizador de Python con una cadena específica como entrada; si está interesado, consulte la implementación de la opción de línea de comando -c en el código fuente de Python).Modules/main.c

Llamar a funciones de Python es fácil. Primero, el programa Python debe pasarle el objeto de función Python de alguna manera. Debe proporcionar una función (o alguna otra interfaz) para hacer esto. Cuando llame a esta función, guarde un puntero a un objeto de función de Python (¡tenga cuidado con Py_INCREF() !) en una variable global, o donde mejor le parezca. Por ejemplo, las siguientes funciones podrían ser parte de una definición de módulo:

PyObject estático * my_callback =  NULL ; PyObject estático * my_set_callback (PyObject * ficticio, PyObject * args)
{
    PyObject * resultado = NULL ; 
    PyObject * temporal; if (PyArg_ParseTuple(args, "O:set_callback" , & temp)) {
         if ( ! PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "el parámetro debe ser invocable" );
            devolver


 

     NULO ; 
        } 
        Py_XINCREF(temp);         /* Agregar una referencia a una nueva devolución de llamada */ 
        Py_XDECREF(my_callback);  /* Desechar la devolución de llamada anterior */ 
        my_callback = temp;       /* Recordar nueva devolución de llamada */ 
        /* Repetitivo para devolver "Ninguno" */ 
        Py_INCREF(Py_None); 
        resultado = Py_Ninguno; 
    } devolver resultado; 
}
    

La función debe registrarse con el intérprete usando el indicador  METH_VARARGS ; esto se describe en la sección Tabla de métodos y funciones de inicialización del módulo . La  función PyArg_ParseTuple() y sus argumentos están documentados en  la sección "Extracción de argumentos de funciones de extensión" .

Las macros Py_XINCREF() y Py_XDECREF() incrementan/disminuyen el recuento de referencias del objeto y son seguras en presencia de un puntero NULL (pero tenga en cuenta que temp no será  NULL en este contexto). Consulte la sección Conteo de referencias para obtener más información sobre ellos.

Más tarde, cuando necesite llamar a la función, llame a la función C  PyObject_CallObject() . Esta función toma dos parámetros, ambos punteros a objetos Python arbitrarios: la función Python y la lista de parámetros. La lista de argumentos siempre debe ser un objeto tupla cuya longitud sea el número de argumentos. Para llamar a una función de Python sin parámetros, pase NULL o una tupla vacía; para llamarla con un parámetro, pase una tupla singleton. Py_BuildValue() Devuelve una tupla cuando la cadena de formato de la tupla consta de cero o más códigos de formato entre paréntesis. Por ejemplo:

argumento int ; 
PyObject * lista de args; 
PyObject * resultado; 
... 
argumento =  123 ; 
... /* Hora de llamar a la devolución de llamada */ 
arglist = Py_BuildValue( "(i)" , arg); 
resultado = PyObject_CallObject(my_callback, arglist); 
Py_DECREF(listaarg);

PyObject_CallObject() devuelve un puntero de objeto Python: este es el valor de retorno de la función Python. PyObject_CallObject() es "neutral en el recuento de referencias" con respecto a sus argumentos. En el ejemplo, se crea una nueva tupla como lista de argumentos, que se agrega inmediatamente después de  llamar a PyObject_CallObject ()  .

El valor de retorno de PyObject_CallObject() es "nuevo": ya sea un objeto completamente nuevo o un objeto existente cuyo recuento de referencias se ha incrementado. Entonces, a menos que desee guardarlo en una variable global, de alguna manera debería obtener el resultado de Py_DECREF() , incluso (¡especialmente!) si no está interesado en su valor.

Sin embargo, antes de hacer esto, es importante comprobar que el valor de retorno no sea NULL . Si es así, la función Python genera una excepción y finaliza. Si el código C que llama, PyObject_CallObject(), se llamó desde Python, ahora debería devolver una indicación de error a su llamador Python para que el intérprete pueda imprimir un seguimiento de la pila, o que el código Python que llama pueda manejar la excepción. Si esto no es posible o deseable, la excepción debe borrarse llamando a  PyErr_Clear() . Por ejemplo:

si (resultado ==  NULL )
     devuelve  NULL ; /* Devolver el error */ 
...usar resultado...
Py_DECREF(resultado);

Dependiendo de la interfaz de función de devolución de llamada de Python deseada, es posible que también deba proporcionar PyObject_CallObject() . En algunos casos, el programa Python también proporciona la lista de argumentos a través de la misma interfaz que especifica la función de devolución de llamada. Luego se puede guardar y utilizar de la misma manera que un objeto de función. En otros casos, es posible que tenga que construir una nueva tupla para pasarla como lista de argumentos. La forma más sencilla es llamar a Py_BuildValue() . Por ejemplo, si desea pasar el código de evento completo, puede usar el siguiente código:

PyObject * lista de args; 
... 
arglist = Py_BuildValue( "(l)" , código de evento); 
resultado = PyObject_CallObject(my_callback, arglist); 
Py_DECREF(listaarg); si (resultado == NULL )
     devuelve NULL ; /* Devolver el error */ /* Aquí tal vez use el resultado */ 
Py_DECREF(resultado);
  

Py_DECREF(arglist)¡Tenga en cuenta que se coloca inmediatamente después de la llamada y antes de la verificación de errores! También tenga en cuenta que este código no está estrictamente completo:  Py_BuildValue() puede quedarse sin memoria, por lo que debe verificarse.

También puede llamar funciones con argumentos de palabras clave utilizando  PyObject_Call() , que admite tanto argumentos como argumentos de palabras clave. Como en el ejemplo anterior, usamos Py_BuildValue() para construir el diccionario.

PyObject * dictado; 
... 
dict = Py_BuildValue( "{s:i}" , "nombre" , val); 
resultado = PyObject_Call(my_callback, NULL , dict); 
Py_DECREF(dict); si (resultado == NULL )
     devuelve NULL ; /* Devolver el error */ /* Aquí tal vez use el resultado */ 
Py_DECREF(resultado);
  

1.7 Extraer parámetros en funciones de extensión

La función PyArg_ParseTuple() se declara de la siguiente manera:

int  PyArg_ParseTuple (PyObject * arg, formato const  char  * , ...);

El parámetro arg debe ser un objeto tupla que contenga la lista de argumentos pasados ​​desde Python a la función C. El parámetro de formato debe ser una cadena de formato, cuya sintaxis se explica en Análisis de parámetros y creación de valores en el Manual de referencia de la API de Python/C . Los argumentos restantes deben ser direcciones de variables cuyos tipos están determinados por la cadena de formato.

Tenga en cuenta que, si bien PyArg_ParseTuple() verifica que el argumento de Python tenga el tipo requerido, no puede verificar la validez de la dirección de la variable C pasada a la llamada: si comete un error allí, su código probablemente fallará o al menos se sobrescribirá. en memoria. ¡Así que ten cuidado!

Tenga en cuenta que cualquier referencia a un objeto Python proporcionada a la persona que llama son  referencias prestadas ; ¡no disminuya su recuento de referencias!

Algunos ejemplos de llamadas:

#define PY_SSIZE_T_CLEAN /* Haz que "s#" use Py_ssize_t en lugar de int. */
 #incluir  <Python.h>
está bien;
int i, j;
largo k, l;
carácter constante  * s; 
Tamaño Py_ssize_t; 
ok = PyArg_ParseTuple(argumentos, "" ); /* Sin argumentos */ /* Llamada Python: f() */ 

    
ok = PyArg_ParseTuple(args, "s" , & s); /* Una cadena */ 
    /* Posible llamada de Python: f('¡vaya!') */
ok = PyArg_ParseTuple(args, "lls" , & k, & l, & s); /* Dos largos y una cadena */ 
    /* Posible llamada de Python: f(1, 2, 'tres') */
ok = PyArg_ParseTuple(args, "(ii)s#" , & i, & j, & s, & size);
    /* Un par de enteros y una cadena, cuyo tamaño también se devuelve */ 
    /* Posible llamada de Python: f((1, 2), 'tres') */
{
     const  char  * archivo;
    const  char  * modo =  "r" ;
    int tamaño buf =  0 ; 
    ok = PyArg_ParseTuple(args, "s|si" , & archivo, & modo, & bufsize);
    /* Una cadena, y opcionalmente otra cadena y un número entero */ 
    /* Posibles llamadas a Python: 
 f('spam') 
 f('spam', 'w') 
 f('spam', 'wb', 100000) */ 
}
{
     int izquierda, arriba, derecha, abajo, h, v; 
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)" ,
              & izquierda, & arriba, & derecha, & abajo, & h, & v);
    /* Un rectángulo y un punto */ 
    /* Posible llamada de Python: 
 f(((0, 0), (400, 300)), (10, 10)) */ 
}
{ 
    Py_complejo c; 
    ok = PyArg_ParseTuple(args, "D:mifunción" , & c);
    /* un complejo, que también proporciona un nombre de función para errores */ 
    /* Posible llamada de Python: myfunction(1+2j) */ 
}

1.8 Parámetros de palabras clave de funciones de extensión

La función PyArg_ParseTupleAndKeywords() se declara de la siguiente manera:

int  PyArg_ParseTupleAndKeywords (PyObject * arg, PyObject * kwdict,
                                 const  char  * formato, char  * kwlist[], ...);

Los parámetros arg y format son los mismos que los de la función  PyArg_ParseTuple() . El argumento kwdict es un diccionario de palabras clave recibido como tercer argumento del tiempo de ejecución de Python . El parámetro kwlist es una lista de cadenas terminada en NULL que se utiliza para identificar los parámetros; los nombres coinciden con la información de tipo en el formato de izquierda a derecha. PyArg_ParseTupleAndKeywords() devuelve verdadero en caso de éxito ; de lo contrario, devuelve falso y genera la excepción apropiada.

notas

 

¡No se pueden analizar tuplas anidadas cuando se utilizan argumentos de palabras clave! Pasar un argumento de palabra clave que no existe en kwlist provocará que se genere un TypeError .

 A continuación se muestra un módulo de ejemplo que utiliza palabras clave, basado en el ejemplo de Geoff Philbrick (  [email protected] ):

#incluye  "Python.h"

 PyObject estático * 
keywdarg_parrot (PyObject * self, PyObject * args, PyObject * keywds) 
{ int voltaje;
    char * estado = "un rígido" ;
    char * acción = "voom" ;
    char * tipo = "Azul noruego" ; carácter estático * kwlist[] = {
         "voltaje" , "estado" ,
          

      
        "tipo" , NULL }; if ( ! PyArg_ParseTupleAndKeywords(args, keywds, "i|sss" , kwlist,
                                      & voltaje, & estado, & acción, & tipo))
         return NULL ; 
    printf( "-- Este loro no tendría %s si le pasaras %i voltios. \n " , 
           acción, voltaje); 
    printf( "-- Precioso plumaje, el %s -- ¡Es %s! \n " , tipo, estado); 
    Py_RETURN_NONE; 
} estático

     



PyMethodDef keywdarg_methods[] = {
     /* La conversión de la función es necesaria ya que los valores de PyCFunction 
 * solo toman dos parámetros de PyObject*, y keywdarg_parrot() toma 
 * tres. 
 */ 
    { "loro" , (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
      "Imprime un sketch encantador en salida estándar." }, 
    { NULL , NULL , 0 , NULL }    /* centinela */ 
}; estructura estática PyModuleDef keywdargmodule = { 
    PyModuleDef_HEAD_INIT,
        
        
        
        

 
    "keywdarg" ,
     NULL ,
     - 1 , 
    keywdarg_methods 
}; 

PyMODINIT_FUNC PyInit_keywdarg ( vacío ) 
{ return PyModule_Create( & keywdargmodule); 
}

    

1.9 Construyendo valores arbitrarios

Esta función es la misma que PyArg_ParseTuple() y se declara de la siguiente manera:

PyObject * Py_BuildValue ( const  char  * formato, ...);

Reconoce un conjunto de unidades de formato similares a las reconocidas por  PyArg_ParseTuple() , excepto que los argumentos (entradas a la función, no salidas) no pueden ser punteros, sino solo valores. Devuelve un nuevo objeto Python adecuado para ser regresado desde una función C llamada desde Python.

Una diferencia con PyArg_ParseTuple() : mientras que este último requiere que su primer argumento sea una tupla (porque las listas de argumentos de Python siempre se representan internamente como tuplas), Py_BuildValue() no siempre construye una tupla. Crea una tupla sólo si su cadena de formato contiene dos o más unidades de formato. Devuelve si la cadena de formato está vacía None; si contiene solo una unidad de formato, devuelve cualquier objeto descrito por esa unidad de formato. Para obligarlo a devolver una tupla de tamaño 0 o 1, incluya la cadena de formato.

Ejemplo (llamada a la izquierda, valor de Python generado a la derecha):

Py_BuildValue("") Ninguno 
Py_BuildValue("i", 123) 123 
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789) 
Py_BuildValue("s", "hola") 'hola' 
Py_BuildValue(" y", "hola") b'hola' 
Py_BuildValue("ss", "hola", "mundo") ('hola', 'mundo') 
Py_BuildValue("s#", "hola", 4) 'infierno' 
Py_BuildValue("y#", "hola", 4) maldito 
Py_BuildValue("()") () 
Py_BuildValue("(i)", 123) (123,) 
Py_BuildValue("(ii)", 123,456) (123, 456) 
Py_BuildValue("(i,i)", 123, 456) (123, 456) 
Py_BuildValue("[i,i]", 123, 456) [123, 456] 
Py_BuildValue("{s :i,s:i}",
              "abc", 123, "def", 456) {'abc': 123, 'def': 456} 
Py_BuildValue("((ii)(ii)) (ii)", 
              1, 2, 3, 4, 5 , 6) (((1, 2), (3, 4)), (5, 6))

1.10 Conteo de referencias

En lenguajes como C o C++, el programador es responsable de asignar y liberar memoria dinámicamente en el montón. En C, esto se  malloc()hace usando la función suma free(). En C++, el uso de los operadores newdeletetiene esencialmente el mismo significado, y restringimos la siguiente discusión al caso de C.

Cada memoria asignada malloc()debería eventualmente devolverse al grupo de memoria disponible en una sola llamada free(). free()Es importante llamar en el momento adecuado. Si la dirección de un bloque se olvida pero free()no se llama, la memoria que ocupaba no se puede reutilizar hasta que finalice el programa. Esto se llama pérdida de memoria . Por otro lado, si un programa llama a un bloque y luego continúa usándolo, entra en conflicto con la reutilización del bloque free()mediante otra llamada. malloc()A esto se le llama utilizar la memoria liberada . Tiene las mismas consecuencias indeseables que hacer referencia a datos no inicializados: volcados de núcleo, resultados erróneos, bloqueos misteriosos.

Una causa común de pérdidas de memoria son las rutas inusuales en el código. Por ejemplo, una función podría asignar un bloque de memoria, realizar algunos cálculos y luego liberar el bloque nuevamente. Los cambios en los requisitos de la función ahora pueden agregar una prueba al cálculo que detecta condiciones de error y puede regresar temprano de la función. Al realizar este tipo de salida prematura, es fácil olvidarse de liberar el bloque de memoria asignado, especialmente si se agrega más adelante al código. Una vez introducidas, estas filtraciones suelen pasar desapercibidas durante largos períodos de tiempo: las salidas de error ocurren sólo en una pequeña fracción de todas las llamadas, y la mayoría de las computadoras modernas tienen amplia memoria virtual, por lo que las fugas solo ocurren durante largos períodos de tiempo. tiempo en el que el proceso se ejecuta con frecuencia cuando se utiliza la función de fuga. entonces,

Dado que Python hace un uso intensivo de malloc()free(), requiere una estrategia para evitar pérdidas de memoria, así como el uso de la memoria liberada. El método elegido se llama recuento de referencias . El principio es simple: cada objeto contiene un contador que se incrementa cuando una referencia al objeto se almacena en algún lugar y se reduce cuando se elimina la referencia al objeto. Cuando el contador llega a cero, se elimina la última referencia al objeto y se libera el objeto.

Otra estrategia se llama recolección automática de basura . (A veces, el recuento de referencias también se denomina estrategia de recolección de basura, por lo que uso "automático" para distinguir los dos). Una de las grandes ventajas de la recolección automática de basura es que el usuario no necesita llamarla explícitamente  free(). (Otra ventaja supuesta es una mejora en la velocidad o el uso de la memoria, pero eso no es un hecho concreto). La desventaja es que no existe un recolector de basura automático verdaderamente portátil para C, mientras que el recuento de referencias se puede implementar de forma portátil (siempre que funcione y esté disponible malloc() ) free(). - esto está garantizado por el estándar C). Quizás algún día esté disponible un recolector de basura automático lo suficientemente portátil para C. Hasta entonces, tendremos que vivir con el recuento de referencias.

Aunque Python utiliza una implementación tradicional de recuento de referencias, también proporciona un detector de ciclos para detectar ciclos de referencia. Esto permite que las aplicaciones no se preocupen por crear referencias circulares directas o indirectas; estas son debilidades de la recolección de basura implementada utilizando únicamente el recuento de referencias. Un bucle de referencia consta de objetos que contienen referencias (posiblemente indirectas) a sí mismos, por lo que cada objeto del bucle tiene un recuento de referencia distinto de cero. Una implementación típica de recuento de referencias no puede recuperar la memoria que pertenece a ningún objeto al que se hace referencia en el bucle, ni la memoria a la que se hace referencia desde un objeto en el bucle, incluso si no hay más referencias al bucle en sí.

El detector de ciclos es capaz de detectar ciclos de basura y reciclarlos. El módulo gc expone un método para ejecutar un detector (la  función Collect() ) , así como la capacidad de configurar la interfaz y deshabilitar el detector en tiempo de ejecución. El detector de bucle se considera un componente opcional; aunque se incluye de forma predeterminada, se puede desactivar en el momento de la compilación mediante una opción de script de configuración en plataformas Unix (incluido Mac OS X). --without-cycle-gcSi el detector de período se desactiva de esta manera, el módulo no estará disponible. gc

1.10.1 Conteo de referencias en Python

Hay dos macros Py_INCREF(x)que Py_DECREF(x)manejan incrementar y disminuir el recuento de referencia. Py_DECREF() también libera el objeto cuando el recuento llega a cero. Para mayor flexibilidad, no se llama directamente, sino a través de un puntero de función en el tipo de objeto objeto. free()Para este propósito (y otros), cada objeto también contiene un puntero a un objeto de su tipo.

La gran pregunta ahora sigue siendo: ¿cuándo utilizar Py_INCREF(x)Py_DECREF(x)? Primero introduzcamos algo de terminología. Nadie es "dueño" de un objeto; sin embargo, usted puede poseer una referencia a un objeto. El recuento de referencias de un objeto ahora se define como el número de referencias que tiene. El propietario de la referencia es responsable de llamar a Py_DECREF() cuando la referencia ya no sea necesaria. La propiedad de una referencia es transferible. Hay tres formas de manejar una referencia propia: pasarla, almacenarla o llamar a Py_DECREF() . Olvidarse de publicar referencias propias puede provocar pérdidas de memoria.

También puedes pedir prestada  una referencia a un objeto2 . Los prestatarios de materiales de referencia no deben llamar a Py_DECREF() . El prestatario no podrá retener el artículo por más tiempo que el propietario del artículo prestado. El uso de una referencia prestada después de que su propietario se haya deshecho de ella conlleva el riesgo de utilizar la memoria liberada y debe evitarse por completo3 .

La ventaja de tomar prestada una referencia en lugar de poseer una referencia es que no tiene que lidiar con el manejo de referencias en todas las rutas posibles en su código; en otras palabras, use una referencia prestada al salir anticipadamente. La desventaja de pedir prestado frente a poseer es que en algunos casos sutiles, en un código aparentemente correcto, se puede utilizar una referencia prestada después de que el propietario de la referencia prestada realmente se haya deshecho de ella.

Una referencia prestada se puede cambiar a una referencia propia llamando a  Py_INCREF() . Esto no afecta el estado del propietario de la referencia de préstamo: crea una nueva referencia de propietario, otorgando al propietario total responsabilidad (el nuevo propietario debe manejar la referencia correctamente como el propietario anterior).

1.10.2 Reglas de propiedad

Siempre que una referencia de objeto se pasa dentro o fuera de una función, forma parte de la especificación de la interfaz de la función, independientemente de si la propiedad se transfiere con la referencia.

La mayoría de las funciones que devuelven una referencia de objeto pasan la propiedad por referencia. En particular, todas las funciones cuya función es crear un nuevo objeto (como PyLong_FromLong() y Py_BuildValue()) pasan la propiedad al destinatario. Incluso si el objeto no es realmente nuevo, aún obtienes la propiedad de la nueva referencia al objeto. Por ejemplo,  PyLong_FromLong() mantiene un caché de valores populares y puede devolver referencias a elementos almacenados en caché.

Muchas funciones que extraen objetos de otros objetos también transfieren propiedad por referencia, como PyObject_GetAttrString() . Sin embargo, la situación es menos clara aquí porque algunas rutinas comunes son excepciones:  PyTuple_GetItem() , PyList_GetItem() , PyDict_GetItem()PyDict_GetItemString() devuelven referencias tomadas de tuplas, listas o diccionarios.

La función PyImport_AddModule() también devuelve una referencia prestada, aunque en realidad puede crear el objeto que devuelve: esto es posible porque la referencia propietaria del objeto se almacena en el archivo sys.modules.

Cuando pasa una referencia de objeto a otra función, normalmente esa función toma prestada esa referencia; si necesita almacenarla, se convierte  en propietario independiente de Py_INCREF() . Hay dos excepciones importantes a esta regla: PyTuple_SetItem()PyList_SetItem() . Estas funciones toman posesión de los elementos que se les pasan, ¡incluso si fallan! (Tenga en cuenta que los amigos de PyDict_SetItem() no asumen la propiedad; son "normales".)

Cuando se llama a una función C desde Python, toma prestadas referencias a sus argumentos del autor de la llamada. La persona que llama posee una referencia al objeto, por lo que la vida útil de la referencia prestada está garantizada hasta que la función regrese. Solo cuando dicha referencia prestada deba almacenarse o pasarse, se debe convertir en una referencia propia llamando a Py_INCREF() .

Las referencias a objetos devueltas por funciones C llamadas desde Python deben ser referencias de propiedad: la propiedad se transfiere de la función a su llamador.

1.10.3. como hielo fino

En algunos casos, el uso aparentemente inofensivo de referencias prestadas puede causar problemas. Estos están relacionados con llamadas implícitas al intérprete, que pueden provocar que el propietario de la referencia se deshaga de ella.

La primera y más importante situación que hay que entender es cuando se utiliza Py_DECREF() en un objeto no relacionado al tomar prestada una referencia a un elemento de la lista. Por ejemplo:


error nulo (PyObject * lista) 
{ 
    PyObject * elemento = PyList_GetItem(lista, 0 ); 

    PyList_SetItem(lista, 1 , PyLong_FromLong( 0L )); 
    PyObject_Print(elemento, salida estándar, 0 ); /* ¡BICHO! */ 
}

La función primero toma prestada la referencia list[0], luego la reemplaza  list[1]con el valor 0y finalmente imprime la referencia prestada. Parece inofensivo, ¿verdad? ¡pero no es la verdad!

Sigamos el flujo de control hacia PyList_SetItem() . La lista contiene referencias a todos sus elementos, por lo que cuando se reemplaza el elemento 1, debe procesar el elemento 1 original. Ahora supongamos que el elemento 1 original es una instancia de una clase definida por el usuario y supongamos además que esta clase define un  método __del__() . Si dicha instancia tiene un recuento de referencias de 1, se llamará a su método __del__() cuando se elimine.

Dado que está escrito en Python, el método __del__() puede ejecutar código Python arbitrario. item¿Podría hacer algo para invalidar la referencia a in  bug()? ¡lo apuestas! Suponiendo que la lista pasada  bug()es accesible para el método __del__() , puede ejecutar una declaración para lograr el efecto y, suponiendo que esa es la última referencia al objeto, liberará la memoria asociada a él, lo que la invalidará.del list[0]item

Una vez que conozca el origen del problema, la solución es simple: aumentar temporalmente el recuento de referencias. La versión correcta de esta función es la siguiente:

void 
no_bug (PyObject * lista) 
{ 
    PyObject * elemento = PyList_GetItem(lista, 0 ); 

    Py_INCREF(elemento); 
    PyList_SetItem(lista, 1 , PyLong_FromLong( 0L )); 
    PyObject_Print(elemento, salida estándar, 0 ); 
    Py_DECREF(elemento); 
}

Esta es una historia real. Las versiones anteriores de Python contenían variaciones de este error, y alguien pasó mucho tiempo en un depurador de C tratando de descubrir por qué falló su método __del__() ...

El segundo caso de problemas de referencia prestada es una variación que involucra hilos. Normalmente, varios subprocesos en el intérprete de Python no se interponen entre sí porque existe un bloqueo global que protege todo el espacio de objetos de Python. Sin embargo, este bloqueo se puede liberar temporalmente usando la macro  Py_BEGIN_ALLOW_THREADS y volver a adquirirlo usando  Py_END_ALLOW_THREADS . Esto es común al bloquear llamadas de E/S para permitir que otros subprocesos usen el procesador mientras esperan que se complete la E/S. Obviamente, la siguiente función tiene el mismo problema que la función anterior:


error nulo (PyObject * lista) 
{ 
    PyObject * elemento = PyList_GetItem(lista, 0 ); 
    Py_BEGIN_ALLOW_THREADS 
    ...alguna llamada de E / S de bloqueo... 
    Py_END_ALLOW_THREADS 
    PyObject_Print(item, stdout, 0 ); /* ¡BICHO! */ 
}

1.10.4. puntero nulo

En términos generales, las funciones que toman referencias a objetos como argumentos no quieren que les pases punteros NULL y, si lo haces, volcarás el núcleo (o provocarás un volcado de núcleo más adelante). Las funciones que devuelven una referencia a un objeto normalmente devuelven NULL simplemente para indicar que ocurrió una excepción. La razón para no probar los argumentos NULL es que las funciones generalmente pasan los objetos que reciben a otras funciones; si cada función probara NULL , habría muchas pruebas redundantes y el código se ejecutaría más lento.

Es mejor probar NULL solo en la "fuente" : cuando se recibe un puntero que puede ser NULLmalloc() , por ejemplo, desde o hacia una función que puede generar una excepción.

Las macros Py_INCREF() y Py_DECREF() no comprueban punteros NULL  ; sin embargo, sus variantes Py_XINCREF() y Py_XDECREF()  sí lo hacen.

Las macros utilizadas para comprobar tipos de objetos específicos (  Pytype_Check()) no comprueban punteros NULL ; además, hay una gran cantidad de código que llama a varias de estas macros sucesivamente para probar objetos con varios tipos esperados diferentes, lo que genera pruebas redundantes. No existe ninguna  variante con verificación NULL .

El mecanismo de llamada a funciones de C garantiza que la lista de argumentos pasada a una función de C ( argsen el ejemplo) nunca será NULL  ; de hecho, garantiza que siempre será una tupla de 4 .

Dejar que los punteros NULL "se escapen" a los usuarios de Python es un grave error.

1.11 Escribir extensiones en C++

Los módulos de extensión se pueden escribir en C++. Hay algunas limitaciones. Si el programa principal (intérprete de Python) está compilado y vinculado mediante un compilador de C, no puede utilizar objetos globales o estáticos con constructores. Esto no es un problema si el programa principal está vinculado mediante un compilador de C++. Se deben utilizar las funciones llamadas por el intérprete de Python (especialmente las funciones de inicialización de módulos). No es necesario incluir los archivos de encabezado de Python: si los símbolos están definidos, ya usan esta forma (todos los compiladores recientes de C++ definen este símbolo).extern "C"extern "C" {...}__cplusplus

1.12 Proporcionar API C para módulos de extensión 

Muchos módulos de extensión simplemente proporcionan nuevas funciones y tipos que se pueden usar desde Python, pero a veces el código de un módulo de extensión también es útil para otros módulos de extensión. Por ejemplo, un módulo de extensión puede implementar un tipo "conjunto" que funcione como una lista sin ordenar. Así como el tipo de lista estándar de Python tiene una API C que permite a los módulos de extensión crear y manipular listas, este nuevo tipo de colección debe tener un conjunto de funciones C para manipulación directa desde otros módulos de extensión.

A primera vista, esto parece simple: simplemente escriba las funciones (no es necesario declararlas  static, por supuesto), proporcione los archivos de encabezado apropiados y documente la API de C. De hecho, esto funcionaría si todos los módulos de extensión estuvieran siempre vinculados estáticamente con el intérprete de Python. Sin embargo, cuando los módulos se utilizan como bibliotecas compartidas, es posible que los símbolos definidos en un módulo no sean visibles para otro módulo. Los detalles de visibilidad dependen del sistema operativo; algunos sistemas utilizan un espacio de nombres global para el intérprete de Python y todos los módulos de extensión (como Windows), mientras que otros sistemas requieren que se importe explícitamente una lista de símbolos cuando el módulo está vinculado (AIX es un ejemplo) o proporcionar una selección de diferentes estrategias (en su mayoría unificadas). Incluso si el símbolo es visible globalmente, es posible que el módulo cuya función desea llamar aún no se haya cargado.

Por lo tanto, los requisitos de portabilidad no hacen suposiciones sobre la visibilidad de los símbolos. Esto significa que  statictodos los símbolos en un módulo de extensión deben declararse excepto las funciones de inicialización del módulo para evitar conflictos de nombres con otros módulos de extensión (como se describe en la sección Tabla de métodos del  módulo y funciones de inicialización ). Esto significa que los símbolos accesibles desde otros módulos de extensión deben exportarse de forma diferente.

Python proporciona un mecanismo especial para pasar información de nivel C (punteros) de un módulo de extensión a otro: cápsulas. Capsule es un tipo de datos de Python que almacena punteros (). Las cápsulas solo se pueden crear y acceder a ellas a través de su API C, pero se pueden transmitir como cualquier otro objeto de Python. En particular, se pueden asignar a nombres en el espacio de nombres del módulo de extensión. Luego, otros módulos de extensión pueden importar el módulo, recuperar el valor de ese nombre y luego recuperar el puntero de la cápsula.void *

Capsule puede exportar la API C del módulo de extensión de varias formas. Cada función puede tener su propia Cápsula, o todos los punteros de la API de C se pueden almacenar en una matriz cuya dirección se publica en la Cápsula. Y las diversas tareas de almacenar y recuperar punteros se pueden distribuir de diferentes maneras entre el módulo que proporciona el código y el módulo cliente.

No importa qué método elijas, es importante nombrar tu cápsula correctamente. La función PyCapsule_New() toma un argumento de nombre (); puede pasar un nombre NULL , pero le recomendamos encarecidamente que especifique uno. Las cápsulas con el nombre adecuado proporcionan cierto grado de seguridad de tipo de tiempo de ejecución; no existe una forma factible de distinguir una cápsula sin nombre de otra.const char *

En particular, las cápsulas utilizadas para exponer las API de C deben denominarse según la siguiente convención:

nombredelmodulo.nombredelatributo 

La función de conveniencia PyCapsule_Import() facilita la carga de la API de C proporcionada a través de la Cápsula, pero solo si el nombre de la Cápsula coincide con esta convención. Este comportamiento brinda a los usuarios de la API de C un alto grado de certeza de que la cápsula que cargan contiene la API de C correcta.

El siguiente ejemplo demuestra un enfoque que coloca la mayor parte de la carga en el escritor del módulo exportado y funciona para un módulo de biblioteca de uso común. Almacena todos los punteros de la API de C (¡solo uno en el ejemplo!) en una matriz de punteros, voidque se convierte en el valor de la Cápsula. El archivo de encabezado correspondiente al módulo proporciona una macro que importa el módulo y recupera su puntero API de C; los módulos cliente solo necesitan llamar a esta macro antes de acceder a la API de C.

El módulo de exportación esspam una modificación del módulo de la sección "Ejemplo simple". Esta función no llama directamente a la función de la biblioteca C, sino que llama a una función que, en realidad, por supuesto hará algo más complicado (como agregar "spam" a cada comando). Esta funcionalidad también se exporta a otros módulos de extensión.spam.system()system()PySpam_System()PySpam_System()

La función PySpam_System()es una función C normal,  staticdeclarada como cualquier otra función:

static  int 
PySpam_System ( const  char  * comando) { sistema de retorno (comando);  }
    

La función spam_system()se modifica de forma sencilla:

PyObject estático * 
spam_system (PyObject * self, PyObject * args) { const char * comando; puntos internos ; if ( ! PyArg_ParseTuple(args, "s" , & comando))  devuelve NULL ;  sts = PySpam_System(comando); devolver PyLong_FromLong(pts);  }
      

     

al comienzo del módulo, inmediatamente después de la línea

#incluir  "Python.h"

Hay que añadir dos líneas más:

#definir SPAM_MODULE 
#incluir  "spammodule.h"

Se utiliza para #defineindicarle al archivo de encabezado que se incluye en el módulo de exportación, no en el módulo de cliente. Finalmente, la función de inicialización del módulo debe ser responsable de inicializar la matriz de punteros de la API de C:

PyMODINIT_FUNC PyInit_spam ( vacío )  {  PyObject * m; vacío estático  * PySpam_API[PySpam_API_pointers];  PyObject * c_api_object;  m = PyModule_Create( & módulo de spam); si (m == NULL ) devuelve NULL ; /* Inicializa la matriz de punteros de la API de C */ PySpam_API[PySpam_System_NUM] = ( void * )PySpam_System; /* Crea una cápsula que contiene la dirección de la matriz de punteros API */ 
  

     

     c_api_object = PyCapsule_New(( void  * )PySpam_API, "spam._C_API" , NULL ); si (c_api_object ! = NULL )  PyModule_AddObject(m, "_C_API" , c_api_object); devolver m;  }

     

Tenga en cuenta PySpam_APIque esto está declarado static; de lo contrario, la matriz de punteros desaparecerá PyInit_spam()al finalizar.

La mayor parte del trabajo está en el archivo de encabezado spammodule.h, como se muestra a continuación:

#ifndef Py_SPAMMODULE_H 
#define Py_SPAMMODULE_H 
#ifdef __cplusplus 
extern  "C" { #endif 

/* Archivo de encabezado para spammodule */ 

/* Funciones API de C */
 #define PySpam_System_NUM 0 
#define PySpam_System_RETURN int 
#define PySpam_System_PROTO (const char *comando) 

/* Número total de punteros API de C */
 #define PySpam_API_pointers 1 


#ifdef SPAM_MODULE 
/* Esta sección se utiliza al compilar spammodule.c */ 

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO; #else /* Esta sección se usa en módulos que usan la API de spammodule */ static void ** PySpam_API;




  

#define PySpam_System \ 
(*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) 

/* Devuelve -1 en caso de error, 0 en caso de éxito. 
* PyCapsule_Import establecerá una excepción si hay un error. 
*/ 
static  int 
import_spam ( void )  {  PySpam_API = ( void  ** )PyCapsule_Import( "spam._C_API" , 0 ); devolver (PySpam_API ! =  NULL ) ?  0  :  - 1 ;  } #endif #ifdef __cplusplus } #endif






#endif /* !definido(Py_SPAMMODULE_H) */

PySpam_System()Para acceder a la función, todo lo que debe hacer el módulo cliente es llamar a la función (o más bien a la macro) en su función de inicialización :import_spam()

PyMODINIT_FUNC PyInit_client ( vacío )  {  PyObject * m; 
 m = PyModule_Create( & módulocliente); si (m ==  NULL ) devuelve  NULL ; si (import_spam() <  0 ) devuelve  NULL ; /* aquí puede realizarse una inicialización adicional */ 
    return m;  }

La principal desventaja de este enfoque es que los archivos spammodule.hson bastante complejos. Sin embargo, la estructura básica de cada función derivada es la misma, por lo que sólo es necesario aprenderla una vez.

Por último, cabe mencionar que las cápsulas proporcionan una funcionalidad adicional que es particularmente útil para la asignación y desasignación de memoria de punteros almacenados en las cápsulas. Los detalles se describen en la sección Cápsulas del Manual de referencia de la API de Python/C y en la implementación de Cápsulas (documentación  Include/pycapsule.hy en la distribución del código fuente de Python).Objects/pycapsule.c

Supongo que te gusta

Origin blog.csdn.net/tianqiquan/article/details/133385121
Recomendado
Clasificación