How-To Argument Clinic

autor

Larry Hastings

Resumen

Argument Clinic es un preprocesador para archivos CPython C. Su propósito es automatizar todo el texto estándar involucrado con la escritura de código de análisis de argumentos para «incorporados». Este documento le muestra cómo convertir su primera función C para que funcione con Argument Clinic y luego presenta algunos temas avanzados sobre el uso de Argument Clinic.

Actualmente, Argument Clinic se considera solo interno para CPython. Su uso no es compatible con archivos fuera de CPython y no se ofrecen garantías con respecto a la compatibilidad con versiones anteriores. En otras palabras: si mantiene una extensión C externa para CPython, puede experimentar con Argument Clinic en su propio código. Pero la versión de Argument Clinic que se envía con la próxima versión de CPython podría ser totalmente incompatible y romper todo su código.

Los objetivos del Argument Clinic

El objetivo principal de Argument Clinic es asumir la responsabilidad de todo el código de análisis de argumentos dentro de CPython. Esto significa que, cuando convierte una función para que funcione con Argument Clinic, esa función ya no debería realizar ninguno de sus propios análisis de argumentos; el código generado por Argument Clinic debería ser una «caja negra» para usted, donde CPython llama al top, y su código se llama en la parte inferior, con PyObject *args (y tal vez PyObject *kwargs) convertido mágicamente en las variables y tipos C que necesita.

Para que Argument Clinic logre su objetivo principal, debe ser fácil de usar. Actualmente, trabajar con la biblioteca de análisis de argumentos de CPython es una tarea ardua que requiere mantener información redundante en un número sorprendente de lugares. Cuando usa Argument Clinic, no tiene que repetirse.

Obviamente, si Argument Clinic no produjo ningún resultado, es porque encontró un error en su entrada. Siga corrigiendo sus errores y vuelva a intentarlo hasta que Argument Clinic procese su archivo sin quejas.

Además, Argument Clinic debe ser lo suficientemente flexible como para trabajar con cualquier enfoque de análisis de argumentos. Python tiene algunas funciones con algunos comportamientos de análisis muy extraños; el objetivo de Argument Clinic es apoyarlos a todos.

Finalmente, la motivación original de Argument Clinic era proporcionar «signaturas» de introspección para las incorporaciones de CPython. Solía ser, las funciones de consulta de introspección lanzarían una excepción si pasaba un archivo incorporado. ¡Con Argument Clinic, eso es cosa del pasado!

Una idea que debe tener en cuenta al trabajar con Argument Clinic: cuanta más información le dé, mejor será su trabajo. Argument Clinic es ciertamente relativamente simple en este momento. Pero a medida que evolucione, se volverá más sofisticado y debería poder hacer muchas cosas interesantes e inteligentes con toda la información que le proporcione.

Conceptos básicos y uso

Argument Clinic se envía con CPython; lo encontrará en Tools/clinic/clinic.py. Si ejecuta ese script, especificando un archivo C como argumento:

$ python3 Tools/clinic/clinic.py foo.c

Argument Clinic escaneará el archivo buscando líneas que se vean exactamente así:

/*[clinic input]

Cuando encuentra uno, lee todo hasta una línea que se ve exactamente así:

[clinic start generated code]*/

Todo lo que se encuentra entre estas dos líneas es entrada para Argument Clinic. Todas estas líneas, incluidas las líneas de comentarios iniciales y finales, se denominan colectivamente un «bloque» de Argument Clinic.

Cuando Argument Clinic analiza uno de estos bloques, genera una salida. Esta salida se reescribe en el archivo C inmediatamente después del bloque, seguida de un comentario que contiene una suma de comprobación. El bloque Argument Clinic ahora tiene este aspecto:

/*[clinic input]
... clinic input goes here ...
[clinic start generated code]*/
... clinic output goes here ...
/*[clinic end generated code: checksum=...]*/

Si ejecuta Argument Clinic en el mismo archivo por segunda vez, Argument Clinic descartará la salida anterior y escribirá la nueva salida con una nueva línea de suma de comprobación. Sin embargo, si la entrada no ha cambiado, la salida tampoco cambiará.

Nunca debe modificar la parte de salida de un bloque de Argument Clinic. En su lugar, cambie la entrada hasta que produzca la salida que desea. (Ese es el propósito de la suma de comprobación: detectar si alguien cambió la salida, ya que estas ediciones se perderían la próxima vez que Argument Clinic escriba una salida nueva).

En aras de la claridad, esta es la terminología que usaremos con Argument Clinic:

  • La primera línea del comentario (/*[clinic input]) es la línea de inicio.

  • La última línea del comentario inicial ([clinic start generated code]*/) es la línea final.

  • La última línea (/*[clinic end generated code: checksum=...]*/) es la línea de suma de comprobación (chemsum line).

  • Entre la línea de inicio y la línea final está el input.

  • Entre la línea final y la línea de suma de comprobación se encuentra la output.

  • Todo el texto colectivamente, desde la línea de inicio hasta la línea de suma de verificación inclusive, es el bloque. (Un bloque que no ha sido procesado con éxito por Argument Clinic todavía no tiene salida o una línea de suma de verificación, pero aún se considera un bloque).

Convirtiendo su primera función

La mejor manera de tener una idea de cómo funciona Argument Clinic es convertir una función para que funcione con ella. Aquí, entonces, están los pasos mínimos que debe seguir para convertir una función para que funcione con Argument Clinic. Tenga en cuenta que para el código que planea registrar en CPython, realmente debería llevar la conversión más lejos, utilizando algunos de los conceptos avanzados que verá más adelante en el documento (como «convertidores de retorno» y «convertidores automáticos»). Pero lo haremos simple para este tutorial para que pueda aprender.

¡Vamos a sumergirnos!

  1. Asegúrese de estar trabajando con una versión recién actualizada de CPython.

  2. Busca un incorporado de Python que llame a PyArg_ParseTuple() o PyArg_ParseTupleAndKeywords(), y que aún no se haya convertido para funcionar con Argument Clinic. Para mi ejemplo, estoy usando _pickle.Pickler.dump().

  3. Si la llamada a la función PyArg_Parse usa cualquiera de las siguientes unidades de formato:

    O&
    O!
    es
    es#
    et
    et#
    

    o si tiene múltiples llamadas a PyArg_ParseTuple(), debes elegir una función diferente. Argument Clinic admite todos estos escenarios. Pero estos son temas avanzados; hagamos algo más simple para su primera función.

    Además, si la función tiene múltiples llamadas a PyArg_ParseTuple() o PyArg_ParseTupleAndKeywords() donde admite diferentes tipos para el mismo argumento, o si la función usa algo además de las funciones PyArg_Parse para analizar sus argumentos, probablemente no sea adecuado para la conversión a Argument Clinic. Argument Clinic no admite funciones genéricas ni parámetros polimórficos.

  4. Agrega la siguiente plantilla sobre la función, creando nuestro bloque

    /*[clinic input]
    [clinic start generated code]*/
    
  5. Corta el docstring y lo pega entre las líneas [clinic], eliminando toda la basura que la convierte en una cadena C entre comillas. Cuando haya terminado, debería tener solo el texto, basado en el margen izquierdo, sin una línea de más de 80 caracteres. (Argument Clinic conservará las sangrías dentro del docstring).

    Si el docstring antiguo tenía una primera línea que parecía una firma de función, elimine esa línea. (El docstring ya no la necesita; cuando use help() en su incorporado en el futuro, la primera línea se creará automáticamente en función de la firma de la función).

    Muestra:

    /*[clinic input]
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  6. Si su cadena de documentos no tiene una línea de «resumen», Argument Clinic se quejará. Así que asegurémonos de que tenga uno. La línea de «resumen» debe ser un párrafo que consta de una sola línea de 80 columnas al comienzo de la cadena de documentos.

    (Nuestro docstring de ejemplo consiste únicamente en una línea de resumen, por lo que el código de muestra no tiene que cambiar para este paso.)

  7. Sobre el docstring, ingrese el nombre de la función, seguido de una línea en blanco. Este debería ser el nombre de Python de la función, y debería ser la ruta de puntos completa a la función; debería comenzar con el nombre del módulo, incluir cualquier submódulo y, si la función es un método en una clase, debe incluir el nombre de la clase también.

    Muestra:

    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  8. Si es la primera vez que ese módulo o clase se utiliza con Argument Clinic en este archivo C, debe declarar el módulo o la clase. La higiene de la clínica de argumentos apropiados prefiere declararlos en un bloque separado en algún lugar cerca de la parte superior del archivo C, de la misma manera que los archivos de inclusión y las estadísticas van en la parte superior. (En nuestro código de muestra, solo mostraremos los dos bloques uno al lado del otro).

    El nombre de la clase y el módulo debe ser el mismo que el visto por Python. Compruebe el nombre definido en PyModuleDef o PyTypeObject según corresponda.

    Cuando declaras una clase, también debes especificar dos aspectos de su tipo en C: la declaración de tipo que usarías para un puntero a una instancia de esta clase y un puntero a PyTypeObject para esto clase.

    Muestra:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  9. Declare cada uno de los parámetros a la función. Cada parámetro debe tener su propia línea. Todas las líneas de parámetros deben tener sangría del nombre de la función y el docstring.

    La forma general de estas líneas de parámetros es la siguiente:

    name_of_parameter: converter
    

    Si el parámetro tiene un valor predeterminado, agréguelo después del convertidor:

    name_of_parameter: converter = default_value
    

    El soporte de Argument Clinic para «valores predeterminados» es bastante sofisticado; por favor vea la sección a continuación sobre valores predeterminados para más información.

    Agrega una línea en blanco debajo de los parámetros.

    ¿Qué es un «convertidor»? Establece tanto el tipo de variable utilizada en C como el método para convertir el valor de Python en un valor de C en tiempo de ejecución. Por ahora, va a utilizar lo que se llama un «convertidor heredado», una sintaxis conveniente destinada a facilitar la migración del código antiguo a Argument Clinic.

    Para cada parámetro, copie la «unidad de formato» para ese parámetro del argumento de formato PyArg_Parse() y especifique eso como su convertidor, como una cadena entre comillas. («unidad de formato» es el nombre formal de la subcadena de caracteres de uno a tres caracteres del parámetro format que le dice a la función de análisis de argumentos cuál es el tipo de variable y cómo convertirla. Para más información sobre las unidades de formato por favor vea Analizando argumentos y construyendo valores.)

    Para unidades de formato de caracteres múltiples como z#, use la cadena completa de dos o tres caracteres.

    Muestra:

     /*[clinic input]
     module _pickle
     class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
     [clinic start generated code]*/
    
     /*[clinic input]
     _pickle.Pickler.dump
    
        obj: 'O'
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  10. Si su función tiene | en la cadena de formato, lo que significa que algunos parámetros tienen valores predeterminados, puede ignorarlo. Argument Clinic infiere qué parámetros son opcionales en función de si tienen o no valores predeterminados.

    Si su función tiene $ en la cadena de caracteres de formato, lo que significa que toma argumentos de solo palabras clave, especifique * en una línea antes del primer argumento de solo palabras clave, con la misma indentación que las líneas de parámetros.

    (_pickle.Pickler.dump no tiene ninguno, por lo que nuestro ejemplo no ha cambiado.)

  11. Si la función C existente llama a PyArg_ParseTuple() (a diferencia de PyArg_ParseTupleAndKeywords()), entonces todos sus argumentos son solo posicionales.

    Para marcar todos los parámetros como solo posicionales en Argument Clinic, agregue un / en una línea después del último parámetro, con la misma sangría que las líneas de parámetros.

    Actualmente esto es todo o nada; o todos los parámetros son solo posicionales o ninguno de ellos lo es. (En el futuro, Argument Clinic puede relajar esta restricción).

    Muestra:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  12. Es útil escribir una cadena de documentos por parámetro para cada parámetro. Pero los docstrings por parámetro son opcionales; puede omitir este paso si lo prefiere.

    A continuación, se explica cómo agregar un docstring por parámetro. La primera línea del docstring por parámetro debe tener más sangría que la definición del parámetro. El margen izquierdo de esta primera línea establece el margen izquierdo para todo el docstring por parámetro; todo el texto que escriba se verá afectado por esta cantidad. Puede escribir todo el texto que desee, en varias líneas si lo desea.

    Muestra:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  13. Guarde y cierre el archivo, luego ejecute Tools/clinic/clinic.py en él. ¡Con suerte, todo funcionó — su bloque ahora tiene salida y se ha generado un archivo .c.h ! Vuelva a abrir el archivo en su editor de texto para ver:

    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
    static PyObject *
    _pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
    /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
    

    Obviamente, si Argument Clinic no produjo ningún resultado, es porque encontró un error en su entrada. Siga corrigiendo sus errores y vuelva a intentarlo hasta que Argument Clinic procese su archivo sin quejas.

    Para facilitar la lectura, la mayor parte del código de pegamento se ha generado en un archivo .c.h. Deberá incluir eso en su archivo .c original, generalmente justo después del bloque del módulo de la clínica:

    #include "clinic/_pickle.c.h"
    
  14. Vuelva a verificar que el código de análisis de argumentos generado por Argument Clinic se ve básicamente igual al código existente.

    Primero, asegúrese de que ambos lugares usen la misma función de análisis de argumentos. El código existente debe llamar a PyArg_ParseTuple() o PyArg_ParseTupleAndKeywords(); asegúrese de que el código generado por Argument Clinic llame a la misma exacta función.

    En segundo lugar, la cadena de formato pasada a PyArg_ParseTuple() o PyArg_ParseTupleAndKeywords() debe ser exactamente la misma que la escrita a mano en la función existente, hasta los dos puntos o punto y coma.

    (Argument Clinic siempre genera sus cadenas de caracteres de formato con un : seguido del nombre de la función. Si la cadena de caracteres de formato del código existente termina con ;, para proporcionar ayuda de uso, este cambio es inofensivo; no se preocupe)

    En tercer lugar, para los parámetros cuyas unidades de formato requieren dos argumentos (como una variable de longitud, una cadena de codificación o un puntero a una función de conversión), asegúrese de que el segundo argumento sea exactamente el mismo entre las dos invocaciones.

    En cuarto lugar, dentro de la parte de salida del bloque, encontrará una macro de preprocesador que define la estructura static PyMethodDef apropiada para este incorporado:

    #define __PICKLE_PICKLER_DUMP_METHODDEF    \
    {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
    

    Esta estructura estática debe ser exactamente la misma que la estructura estática existente PyMethodDef para este incorporado.

    Si alguno de estos elementos difiere de alguna manera, ajuste la especificación de la función de Argument Clinic y vuelva a ejecutar Tools/clinic/clinic.py hasta que sean iguales.

  15. Observe que la última línea de su salida es la declaración de su función «impl». Aquí es donde va la implementación incorporada. Elimine el prototipo existente de la función que está modificando, pero deje la llave de apertura. Ahora elimine su código de análisis de argumentos y las declaraciones de todas las variables en las que vierte los argumentos. Observe cómo los argumentos de Python ahora son argumentos para esta función implícita; si la implementación usó nombres diferentes para estas variables, corríjalo.

    Reiteremos, solo porque es un poco extraño. Su código ahora debería verse así:

    static return_type
    your_function_impl(...)
    /*[clinic end generated code: checksum=...]*/
    {
    ...
    

    Argument Clinic generó la línea de suma de comprobación y el prototipo de función justo encima de ella. Debe escribir las llaves de apertura (y cierre) para la función y la implementación en el interior.

    Muestra:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    /*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
    PyDoc_STRVAR(__pickle_Pickler_dump__doc__,
    "Write a pickled representation of obj to the open file.\n"
    "\n"
    ...
    static PyObject *
    _pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj)
    /*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
    {
        /* Check whether the Pickler was initialized correctly (issue3664).
           Developers often forget to call __init__() in their subclasses, which
           would trigger a segfault without this check. */
        if (self->write == NULL) {
            PyErr_Format(PicklingError,
                         "Pickler.__init__() was not called by %s.__init__()",
                         Py_TYPE(self)->tp_name);
            return NULL;
        }
    
        if (_Pickler_ClearBuffer(self) < 0)
            return NULL;
    
        ...
    
  16. ¿Recuerda la macro con la estructura PyMethodDef para esta función? Busque la estructura existente PyMethodDef para esta función y la reemplaza con una referencia a la macro. (Si el incorporado está en el alcance del módulo, esto probablemente estará muy cerca del final del archivo; si el incorporado es un método de clase, probablemente estará debajo pero relativamente cerca de la implementación).

    Tenga en cuenta que el cuerpo de la macro contiene una coma al final. Entonces, cuando reemplace la estructura static PyMethodDef existente con la macro, no agregue una coma al final.

    Muestra:

    static struct PyMethodDef Pickler_methods[] = {
        __PICKLE_PICKLER_DUMP_METHODDEF
        __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
        {NULL, NULL}                /* sentinel */
    };
    
  17. Compile y luego ejecute las partes relevantes del conjunto de pruebas de regresión. Este cambio no debería introducir nuevas advertencias o errores en tiempo de compilación, y no debería haber ningún cambio visible desde el exterior en el comportamiento de Python.

    Bueno, excepto por una diferencia: inspect.signature() ejecutar en su función ahora debería proporcionar una firma válida!

    ¡Felicitaciones, ha adaptado su primera función para trabajar con Argument Clinic!

Temas avanzados

Ahora que ha tenido algo de experiencia trabajando con Argument Clinic, es hora de algunos temas avanzados.

Valores predeterminados simbólicos

El valor predeterminado que proporcione para un parámetro no puede ser una expresión arbitraria. Actualmente, lo siguiente se admite explícitamente:

  • Constantes numéricas (enteros y flotantes)

  • Constantes de cadena de caracteres

  • True, False, y None

  • Constantes simbólicas simples como sys.maxsize, que debe comenzar con el nombre del módulo

En caso de que tenga curiosidad, esto se implementa en from_builtin() en Lib/inspect.py.

(En el futuro, esto puede necesitar ser aún más elaborado, para permitir expresiones completas como CONSTANT - 1.)

Cambiar el nombre de las funciones y variables C generadas por Argument Clinic

Argument Clinic nombra automáticamente las funciones que genera para usted. Ocasionalmente, esto puede causar un problema, si el nombre generado choca con el nombre de una función C existente. Hay una solución sencilla: anule los nombres utilizados para las funciones de C. Simplemente agregue la palabra clave "as" a la línea de declaración de su función, seguida del nombre de la función que desea usar. Argument Clinic usará ese nombre de función para la función base (generada), luego agregará "_impl" al final y lo usará para el nombre de la función impl.

Por ejemplo, si quisiéramos cambiar el nombre de las funciones de C generadas para pickle.Pickler.dump, se vería así:

/*[clinic input]
pickle.Pickler.dump as pickler_dumper

...

La función base ahora se llamaría pickler_dumper(), y la función implícita ahora se llamaría pickler_dumper_impl().

De manera similar, es posible que tenga un problema en el que desee asignar un nombre específico de Python a un parámetro, pero ese nombre puede ser inconveniente en C. Argument Clinic le permite asignar nombres diferentes a un parámetro en Python y en C, usando el mismo "as" como sintaxis:

/*[clinic input]
pickle.Pickler.dump

    obj: object
    file as file_obj: object
    protocol: object = NULL
    *
    fix_imports: bool = True

Aquí, el nombre usado en Python (en la firma y el arreglo de keywords) sería file, pero la variable C se llamaría file_obj.

¡También puede usar esto para cambiar el nombre del parámetro self!

Convirtiendo funciones usando PyArg_UnpackTuple

Para convertir una función que analiza sus argumentos con PyArg_UnpackTuple(), simplemente escribe todos los argumentos, especificando cada uno como un object. Puede especificar el argumento type para convertir el tipo según corresponda. Todos los argumentos deben estar marcados como solo posicionales (agregue un / en una línea después del último argumento).

Actualmente, el código generado usará PyArg_ParseTuple(), pero esto cambiará pronto.

Grupos opcionales

Algunas funciones heredadas tienen un enfoque complicado para analizar sus argumentos: cuentan el número de argumentos posicionales, luego usan una instrucción switch para llamar a una de varias llamadas diferentes PyArg_ParseTuple() dependiendo de cuántos argumentos posicionales existen. (Estas funciones no pueden aceptar argumentos de solo palabras clave). Este enfoque se usó para simular argumentos opcionales antes de que se creara PyArg_ParseTupleAndKeywords().

Si bien las funciones que utilizan este enfoque a menudo se pueden convertir para usar PyArg_ParseTupleAndKeywords(), argumentos opcionales y valores predeterminados, no siempre es posible. Algunas de estas funciones heredadas tienen comportamientos PyArg_ParseTupleAndKeywords() no admite directamente. El ejemplo más obvio es la función incorporada range(), que tiene un argumento opcional en el lado izquierdo de su argumento requerido. Otro ejemplo es curses.window.addch(), que tiene un grupo de dos argumentos que siempre deben especificarse juntos. (Los argumentos se denominan x e y; si llama a la función pasando x, también debe pasar y, y si no pasa x tampoco puede pasar y.)

En cualquier caso, el objetivo de Argument Clinic es admitir el análisis de argumentos para todas las incorporaciones CPython existentes sin cambiar su semántica. Por lo tanto, Argument Clinic admite este enfoque alternativo de análisis, utilizando lo que se denominan grupos opcionales. Los grupos opcionales son grupos de argumentos que deben pasarse todos juntos. Pueden estar a la izquierda o la derecha de los argumentos requeridos. Solo se pueden usar con parámetros de solo posición.

Nota

Los grupos opcionales solo están pensados para su uso al convertir funciones que realizan múltiples llamadas a PyArg_ParseTuple()! Las funciones que usan cualquier otro enfoque para analizar argumentos deben casi nunca convertirse a Argument Clinic usando grupos opcionales. Las funciones que utilizan grupos opcionales actualmente no pueden tener firmas precisas en Python, porque Python simplemente no comprende el concepto. Evite el uso de grupos opcionales siempre que sea posible.

Para especificar un grupo opcional, agregue un [ en una línea antes de los parámetros que desea agrupar y un ] en una línea después de estos parámetros. Como ejemplo, así es como curses.window.addch usa grupos opcionales para hacer que los primeros dos parámetros y el último parámetro sean opcionales:

/*[clinic input]

curses.window.addch

    [
    x: int
      X-coordinate.
    y: int
      Y-coordinate.
    ]

    ch: object
      Character to add.

    [
    attr: long
      Attributes for the character.
    ]
    /

...

Notas:

  • Para cada grupo opcional, se pasará un parámetro adicional a la función impl que representa al grupo. El parámetro será un int llamado grupo_{direction}_{number}, donde {direction} es right o left dependiendo de si el grupo está antes o después los parámetros requeridos, y {number} es un número que aumenta monótonamente (comenzando en 1) que indica qué tan lejos está el grupo de los parámetros requeridos. Cuando se llama a impl, este parámetro se establecerá en cero si este grupo no se usó, y se establecerá en un valor distinto de cero si se usó este grupo. (Por usado o no usado, me refiero a si los parámetros recibieron argumentos en esta invocación).

  • Si no hay argumentos requeridos, los grupos opcionales se comportarán como si estuvieran a la derecha de los argumentos requeridos.

  • En el caso de ambigüedad, el código de análisis de argumentos favorece los parámetros de la izquierda (antes de los parámetros requeridos).

  • Los grupos opcionales solo pueden contener parámetros posicionales.

  • Los grupos opcionales son solo destinados al código heredado. No utilice grupos opcionales para el código nuevo.

Usar convertidores de Argument Clinic reales, en lugar de «convertidores heredados»

Para ahorrar tiempo y minimizar cuánto necesita aprender para lograr su primer puerto a Argument Clinic, el tutorial anterior le indica que use «convertidores heredados». Los «convertidores heredados» son una conveniencia, diseñados explícitamente para facilitar la migración del código existente a Argument Clinic. Y para ser claros, su uso es aceptable al portar código para Python 3.4.

Sin embargo, a largo plazo probablemente queramos que todos nuestros bloques utilicen la sintaxis real de Argument Clinic para los convertidores. ¿Por qué? Un par de razones:

  • Los convertidores adecuados son mucho más fáciles de leer y más claros en su intención.

  • Hay algunas unidades de formato que no se admiten como «convertidores heredados», porque requieren argumentos y la sintaxis del convertidor heredado no admite la especificación de argumentos.

  • En el futuro, es posible que tengamos una nueva biblioteca de análisis de argumentos que no esté restringida a lo que PyArg_ParseTuple() admite; esta flexibilidad no estará disponible para los parámetros que utilizan convertidores heredados.

Por lo tanto, si no le importa un poco de esfuerzo adicional, utilice los convertidores normales en lugar de los convertidores heredados.

En pocas palabras, la sintaxis de los convertidores de Argument Clinic (no heredados) parece una llamada a una función de Python. Sin embargo, si no hay argumentos explícitos para la función (todas las funciones toman sus valores predeterminados), puede omitir los paréntesis. Por tanto, bool y bool() son exactamente los mismos convertidores.

Todos los argumentos para los convertidores de Argument Clinic son solo de palabras clave. Todos los convertidores de Argument Clinic aceptan los siguientes argumentos:

c_default

El valor predeterminado para este parámetro cuando se define en C. Específicamente, será el inicializador de la variable declarada en la «función de análisis». Consulte la sección sobre valores predeterminados para saber cómo usar esto. Especificado como una cadena de caracteres.

annotation

El valor de anotación para este parámetro. Actualmente no es compatible, porque PEP 8 exige que la biblioteca de Python no use anotaciones.

Además, algunos convertidores aceptan argumentos adicionales. Aquí hay una lista de estos argumentos, junto con sus significados:

accept

Un conjunto de tipos de Python (y posiblemente pseudo-tipos); esto restringe el argumento permitido de Python a valores de estos tipos. (Esta no es una infraestructura de propósito general; por regla general, solo admite listas específicas de tipos como se muestra en la tabla de convertidores heredados).

Para aceptar None, agregue NoneType a este conjunto.

bitwise

Solo se admite para enteros sin signo. El valor entero nativo de este argumento de Python se escribirá en el parámetro sin ninguna verificación de rango, incluso para valores negativos.

converter

Solo compatible con el convertidor de objetos. Especifica el nombre de una «función de conversión» C para convertir este objeto en un tipo nativo.

encoding

Solo compatible con cadenas de caracteres. Especifica la codificación que se utilizará al convertir esta cadena de un valor Python str (Unicode) en un valor char * de C.

subclass_of

Solo compatible con el convertidor de objetos. Requiere que el valor de Python sea una subclase de un tipo de Python, como se expresa en C.

type

Solo compatible con los convertidores de object y self. Especifica el tipo C que se utilizará para declarar la variable. El valor predeterminado es "PyObject *".

zeroes

Solo compatible con cadenas. Si es verdadero, se permiten bytes NUL incrustados ('\\0') dentro del valor. La longitud de la cadena se pasará a la función impl, justo después del parámetro de cadena, como un parámetro llamado <parameter_name>_length.

Tenga en cuenta que no todas las combinaciones posibles de argumentos funcionarán. Por lo general, estos argumentos se implementan mediante unidades de formato PyArg_ParseTuple específicas, con un comportamiento específico. Por ejemplo, actualmente no puede llamar a unsigned_short sin especificar también bitwise=True. Aunque es perfectamente razonable pensar que esto funcionaría, esta semántica no se asigna a ninguna unidad de formato existente. Entonces, Argument Clinic no lo admite. (O, al menos, todavía no).

A continuación se muestra una tabla que muestra el mapeo de convertidores heredados en convertidores de Argument Clinic reales. A la izquierda está el convertidor heredado, a la derecha está el texto con el que lo reemplazaría.

'B'

unsigned_char(bitwise=True)

'b'

unsigned_char

'c'

char

'C'

int(accept={str})

'd'

double

'D'

Py_complex

'es'

str(encoding='name_of_encoding')

'es#'

str(encoding='name_of_encoding', zeroes=True)

'et'

str(encoding='name_of_encoding', accept={bytes, bytearray, str})

'et#'

str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True)

'f'

float

'h'

short

'H'

unsigned_short(bitwise=True)

'i'

int

'I'

unsigned_int(bitwise=True)

'k'

unsigned_long(bitwise=True)

'K'

unsigned_long_long(bitwise=True)

'l'

long

'L'

long long

'n'

Py_ssize_t

'O'

object

'O!'

object(subclass_of='&PySomething_Type')

'O&'

object(converter='name_of_c_function')

'p'

bool

'S'

PyBytesObject

's'

str

's#'

str(zeroes=True)

's*'

Py_buffer(accept={buffer, str})

'U'

unicode

'u'

Py_UNICODE

'u#'

Py_UNICODE(zeroes=True)

'w*'

Py_buffer(accept={rwbuffer})

'Y'

PyByteArrayObject

'y'

str(accept={bytes})

'y#'

str(accept={robuffer}, zeroes=True)

'y*'

Py_buffer

'Z'

Py_UNICODE(accept={str, NoneType})

'Z#'

Py_UNICODE(accept={str, NoneType}, zeroes=True)

'z'

str(accept={str, NoneType})

'z#'

str(accept={str, NoneType}, zeroes=True)

'z*'

Py_buffer(accept={buffer, str, NoneType})

Como ejemplo, aquí está nuestra muestra pickle.Pickler.dump usando el convertidor adecuado:

/*[clinic input]
pickle.Pickler.dump

    obj: object
        The object to be pickled.
    /

Write a pickled representation of obj to the open file.
[clinic start generated code]*/

Una ventaja de los convertidores reales es que son más flexibles que los convertidores heredados. Por ejemplo, el convertidor unsigned_int (y todos los convertidores unsigned_) se pueden especificar sin bitwise=True. Su comportamiento predeterminado realiza una verificación de rango en el valor y no aceptarán números negativos. ¡No puedes hacer eso con un convertidor heredado!

Argument Clinic le mostrará todos los convertidores que tiene disponibles. Para cada convertidor, le mostrará todos los parámetros que acepta, junto con el valor predeterminado para cada parámetro. Simplemente ejecute Tools/clinic/clinic.py --converters para ver la lista completa.

Py_buffer

Cuando se utiliza el convertidor Py_buffer (o los convertidores heredados 's*', 'w*', '*y' o 'z*' ), no debes llamar a PyBuffer_Release() en el búfer provisto. Argument Clinic genera código que lo hace por usted (en la función de análisis).

Convertidores avanzados

¿Recuerda esas unidades de formato que omitió por primera vez porque eran avanzadas? Aquí le mostramos cómo manejarlas también.

El truco es que todas esas unidades de formato toman argumentos, ya sean funciones de conversión o tipos, o cadenas que especifican una codificación. (Pero los «convertidores heredados» no admiten argumentos. Por eso los omitimos para su primera función). El argumento que especificó para la unidad de formato ahora es un argumento para el convertidor; este argumento es converter (para O&), subclass_of (para O!) o encoding (para todas las unidades de formato que comienzan con e).

Al usar subclass_of, es posible que también desee usar el otro argumento personalizado para object(): type, que le permite establecer el tipo que realmente se usa para el parámetro. Por ejemplo, si desea asegurarse de que el objeto es una subclase de PyUnicode_Type, probablemente desee utilizar el convertidor object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type').

Un posible problema con el uso de Argument Clinic: elimina cierta flexibilidad posible para las unidades de formato que comienzan con e. Al escribir una llamada PyArg_Parse a mano, teóricamente podrías decidir en tiempo de ejecución qué cadena de codificación pasar a PyArg_ParseTuple(). Pero ahora esta cadena debe estar codificada en tiempo de preprocesamiento de Argument-Clinic. Esta limitación es deliberada; hizo que el soporte de esta unidad de formato fuera mucho más fácil y puede permitir futuras optimizaciones. Esta restricción no parece irrazonable; el propio CPython siempre pasa cadenas de codificación estáticas codificadas para parámetros cuyas unidades de formato comienzan con e.

Valores predeterminados de los parámetros

Los valores predeterminados de los parámetros pueden ser cualquiera de varios valores. En su forma más simple, pueden ser literales string, int o float:

foo: str = "abc"
bar: int = 123
bat: float = 45.6

También pueden usar cualquiera de las constantes incorporadas de Python:

yep:  bool = True
nope: bool = False
nada: object = None

También hay soporte especial para un valor predeterminado de NULL y para expresiones simples, documentadas en las siguientes secciones.

El valor predeterminado NULL

Para los parámetros de cadena de caracteres y objeto, puede establecerlos en None para indicar que no hay ningún valor predeterminado. Sin embargo, eso significa que la variable C se inicializará en Py_None. Por conveniencia, hay un valor especial llamado NULL solo por esta razón: desde la perspectiva de Python se comporta como un valor predeterminado de None, pero la variable C se inicializa con NULL.

Expresiones especificadas como valores por defecto

El valor predeterminado de un parámetro puede ser más que un valor literal. Puede ser una expresión completa, utilizando operadores matemáticos y buscando atributos en objetos. Sin embargo, este soporte no es exactamente simple, debido a una semántica no obvia.

Considere el siguiente ejemplo:

foo: Py_ssize_t = sys.maxsize - 1

sys.maxsize puede tener diferentes valores en diferentes plataformas. Por lo tanto, Argument Clinic no puede simplemente evaluar esa expresión localmente y codificarla en C. Por lo tanto, almacena el valor predeterminado de tal manera que se evaluará en tiempo de ejecución, cuando el usuario solicite la firma de la función.

¿Qué espacio de nombres está disponible cuando se evalúa la expresión? Se evalúa en el contexto del módulo del que procede el incorporado. Entonces, si su módulo tiene un atributo llamado «max_widgets», simplemente puede usarlo:

foo: Py_ssize_t = max_widgets

Si el símbolo no se encuentra en el módulo actual, falla para buscar en sys.modules. Así es como puede encontrar sys.maxsize, por ejemplo. (Dado que no sabe de antemano qué módulos cargará el usuario en su intérprete, es mejor limitarse a los módulos que están precargados por el propio Python).

La evaluación de los valores predeterminados solo en tiempo de ejecución significa que Argument Clinic no puede calcular el valor predeterminado de C equivalente correcto. Entonces necesita decirlo explícitamente. Cuando usa una expresión, también debe especificar la expresión equivalente en C, usando el parámetro c_default para el convertidor:

foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1

Otra complicación: Argument Clinic no puede saber de antemano si la expresión que proporciona es válida o no. Lo analiza para asegurarse de que parece legal, pero no puede realmente saberlo. ¡Debe tener mucho cuidado al usar expresiones para especificar valores que están garantizados para ser válidos en tiempo de ejecución!

Finalmente, dado que las expresiones deben ser representables como valores C estáticos, existen muchas restricciones sobre las expresiones legales. Aquí hay una lista de funciones de Python que no está autorizado a usar:

  • Llamadas a funciones.

  • Declaraciones if en línea (3 if foo else 5).

  • Desempaque automático de secuencia (*[1, 2, 3]).

  • Comprensiones de list/set/dict y expresiones generadoras.

  • Literales tuple/list/set/dict.

Usando un convertidor de retorno

De forma predeterminada, la función implícita Argument Clinic genera para usted retorna PyObject *. Pero su función C a menudo calcula algún tipo de C, luego lo convierte en el PyObject * en el último momento. Argument Clinic se encarga de convertir sus entradas de tipos de Python en tipos C nativos; ¿por qué no convertir su valor de retorno de un tipo C nativo en un tipo Python también?

Eso es lo que hace un «convertidor de retorno». Cambia su función impl para retornar algún tipo de C, luego agrega código a la función generada (no implícita) para manejar la conversión de ese valor en el PyObject * apropiado.

La sintaxis de los convertidores de retorno es similar a la de los convertidores de parámetros. Especifica el convertidor de retorno como si fuera una anotación de retorno en la función en sí. Los convertidores de retorno se comportan de la misma manera que los convertidores de parámetros; aceptan argumentos, todos los argumentos son solo palabras clave y, si no está cambiando ninguno de los argumentos predeterminados, puede omitir los paréntesis.

(Si utiliza tanto "as" `` *y* un convertidor de retorno para su función, el ``"as" debe aparecer antes del convertidor de retorno.)

Hay una complicación adicional al usar convertidores de retorno: ¿cómo indica que se ha producido un error? Normalmente, una función retorna un puntero válido (no NULL) para el éxito y NULL para el error. Pero si usa un convertidor de retorno de enteros, todos los enteros son válidos. ¿Cómo puede Argument Clinic detectar un error? Su solución: cada convertidor de retorno busca implícitamente un valor especial que indica un error. Si retorna ese valor y se ha establecido un error (PyErr_Occurred() retorna un valor verdadero), el código generado propagará el error. De lo contrario, codificará el valor que retorna como de costumbre.

Actualmente, Argument Clinic solo admite unos pocos convertidores de retorno:

bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault

Ninguno de estos toma parámetros. Para los tres primeros, retorna -1 para indicar error. Para DecodeFSDefault, el tipo de retorno es const char *; retorna un puntero NULL para indicar un error.

(También hay un convertidor experimental NoneType, que le permite retornar Py_None en caso de éxito o NULL en caso de falla, sin tener que incrementar el recuento de referencias en Py_None. seguro que agrega suficiente claridad para que valga la pena usarlo)

Para ver todos los convertidores retornados que admite Argument Clinic, junto con sus parámetros (si los hay), simplemente ejecute Tools/clinic/clinic.py --converters para ver la lista completa.

Clonando funciones existentes

Si tiene varias funciones que parecen similares, es posible que pueda utilizar la función «clone» de Clinic. Cuando clona una función existente, reutiliza:

  • sus parámetros, incluyendo

    • sus nombres,

    • sus convertidores, con todos los parámetros,

    • sus valores predeterminados,

    • sus docstrings por parámetro,

    • su kind (ya sea solo posicional, posicional o por palabra clave, o solo por palabra clave), y

  • su convertidor de retorno.

Lo único que no se ha copiado de la función original es su docstring; la sintaxis le permite especificar un nuevo docstring.

Aquí está la sintaxis para clonar una función:

/*[clinic input]
module.class.new_function [as c_basename] = module.class.existing_function

Docstring for new_function goes here.
[clinic start generated code]*/

(Las funciones pueden estar en diferentes módulos o clases. Escribí module.class en la muestra solo para ilustrar que debe usar la ruta completa a ambas funciones.)

Lo sentimos, no hay sintaxis para clonar parcialmente una función o clonar una función y luego modificarla. La clonación es una propuesta de todo o nada.

Además, la función desde la que está clonando debe haberse definido previamente en el archivo actual.

Llamando código Python

El resto de los temas avanzados requieren que escriba código Python que vive dentro de su archivo C y modifica el estado de ejecución de Argument Clinic. Esto es simple: simplemente define un bloque de Python.

Un bloque Python utiliza diferentes líneas delimitadoras que un bloque de función de la Argument Clinic. Se parece a esto:

/*[python input]
# python code goes here
[python start generated code]*/

Todo el código dentro del bloque de Python se ejecuta en el momento en que se analiza. Todo el texto escrito en stdout dentro del bloque se redirige a la «salida» después del bloque.

Como ejemplo, aquí hay un bloque de Python que agrega una variable entera estática al código C

/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/

Usando un «auto convertidor»

Argument Clinic agrega automáticamente un parámetro «self» para usted usando un convertidor predeterminado. Establece automáticamente el tipo de este parámetro en el «puntero a una instancia» que especificó cuando declaró el tipo. Sin embargo, puede anular el convertidor de Argument Clinic y especificar uno usted mismo. Simplemente agregue su propio parámetro self como el primer parámetro en un bloque y asegúrese de que su convertidor sea una instancia de self_converter o una subclase del mismo.

¿Qué sentido tiene ? Esto le permite anular el tipo de self o darle un nombre predeterminado diferente.

¿Cómo especifica el tipo personalizado al que desea transmitir self? Si solo tiene una o dos funciones con el mismo tipo para self, puede usar directamente el convertidor self existente de Argument Clinic, pasando el tipo que desea usar como parámetro de type:

/*[clinic input]

_pickle.Pickler.dump

  self: self(type="PicklerObject *")
  obj: object
  /

Write a pickled representation of the given object to the open file.
[clinic start generated code]*/

Por otro lado, si tiene muchas funciones que usarán el mismo tipo para self, es mejor crear su propio convertidor, subclasificando self_converter pero sobrescribiendo el miembro `` type``:

/*[python input]
class PicklerObject_converter(self_converter):
    type = "PicklerObject *"
[python start generated code]*/

/*[clinic input]

_pickle.Pickler.dump

  self: PicklerObject
  obj: object
  /

Write a pickled representation of the given object to the open file.
[clinic start generated code]*/

Escribiendo un convertidor personalizado

Como dijimos en la sección anterior… ¡puedes escribir tus propios convertidores! Un convertidor es simplemente una clase de Python que hereda de CConverter. El propósito principal de un convertidor personalizado es si tiene un parámetro que usa la unidad de formato O&; analizar este parámetro significa llamar a PyArg_ParseTuple() «función de conversión».

Su clase de convertidor debe llamarse *something*_converter. Si el nombre sigue esta convención, entonces su clase de convertidor se registrará automáticamente con Argument Clinic; su nombre será el nombre de su clase con el sufijo _converter eliminado. (Esto se logra con una metaclase).

No debe subclasificar CConverter.__init__. En su lugar, debe escribir una función converter_init(). converter_init() siempre acepta un parámetro self; después de eso, todos los parámetros adicionales deben ser solo palabras clave. Cualquier argumento que se pase al convertidor en Argument Clinic se pasará a su converter_init().

Hay algunos miembros adicionales de CConverter que tal vez desee especificar en su subclase. Aquí está la lista actual:

type

El tipo C que se utilizará para esta variable. type debe ser una cadena de Python que especifique el tipo, por ejemplo int. Si se trata de un tipo de puntero, la cadena de tipo debe terminar con '*'.

default

El valor predeterminado de Python para este parámetro, como un valor de Python. O el valor mágico unspecified si no hay ningún valor predeterminado.

py_default

default como debería aparecer en el código Python, como una cadena. O None si no hay un valor predeterminado.

c_default

default como debería aparecer en el código C, como una cadena de caracteres. O None si no hay un valor predeterminado.

c_ignored_default

El valor predeterminado utilizado para inicializar la variable C cuando no hay ningún valor predeterminado, pero no especificar un valor predeterminado puede resultar en una advertencia de «variable no inicializada». Esto puede suceder fácilmente cuando se utilizan grupos de opciones, aunque el código escrito correctamente nunca utilizará este valor, la variable se pasa al impl, y el compilador de C se quejará del «uso» del valor no inicializado. Este valor siempre debe ser una cadena de caracteres no vacía.

converter

El nombre de la función de conversión de C, como una cadena de caracteres.

impl_by_reference

Un valor booleano. Si es verdadero, Argument Clinic agregará un & delante del nombre de la variable al pasarlo a la función impl.

parse_by_reference

Un valor booleano. Si es verdadero, Argument Clinic agregará un & delante del nombre de la variable al pasarlo a PyArg_ParseTuple().

Aquí está el ejemplo más simple de un convertidor personalizado, de Modules/zlibmodule.c:

/*[python input]

class ssize_t_converter(CConverter):
    type = 'Py_ssize_t'
    converter = 'ssize_t_converter'

[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=35521e4e733823c7]*/

Este bloque agrega un convertidor a Argument Clinic llamado ssize_t. Los parámetros declarados como ssize_t se declararán como tipo Py_ssize_t y serán analizados por la unidad de formato 'O&', que llamará a la función de conversión ssize_t_converter. Las variables ssize_t admiten automáticamente los valores predeterminados.

Los convertidores personalizados más sofisticados pueden insertar código C personalizado para manejar la inicialización y la limpieza. Puede ver más ejemplos de convertidores personalizados en el árbol de fuentes de CPython; grep los archivos C para la cadena CConverter.

Escribiendo un convertidor de retorno personalizado

Escribir un convertidor de retorno personalizado es muy parecido a escribir un convertidor personalizado. Excepto que es algo más simple, porque los convertidores de retorno son en sí mismos mucho más simples.

Los convertidores de retorno deben tener una subclase de CReturnConverter. Todavía no hay ejemplos de convertidores de retorno personalizados, porque todavía no se utilizan ampliamente. Si desea escribir su propio convertidor de retorno, lea Tools/clinic/clinic.py, específicamente la implementación de CReturnConverter y todas sus subclases.

METH_O y METH_NOARGS

Para convertir una función usando METH_O, asegúrese de que el único argumento de la función esté usando el convertidor de object y marque los argumentos como solo posicional:

/*[clinic input]
meth_o_sample

     argument: object
     /
[clinic start generated code]*/

Para convertir una función usando METH_NOARGS, simplemente no especifique ningún argumento.

Aún puede usar un autoconvertidor, un convertidor de retorno y especificar un argumento de tipo para el convertidor de objetos para METH_O.

funciones tp_new y tp_init

Puede convertir las funciones tp_new y tp_init. Simplemente nómbrelas __new__ o __init__ según corresponda. Notas:

  • El nombre de la función generado para __new__ no termina en __new__ como lo haría por defecto. Es solo el nombre de la clase, convertido en un identificador C válido.

  • No se genera ningún PyMethodDef #define para estas funciones.

  • funciones __init__ retornan int, no PyObject *.

  • Utilice docstring como la clase de documentación.

  • Aunque las funciones __new__ `` y ``__init__ siempre deben aceptar tanto los objetos args como los kwargs, al realizar la conversión puede especificar cualquier firma para estas funciones que desee. (Si su función no admite palabras clave, la función de análisis generada generará una excepción si recibe alguna).

Cambiar y redirigir la salida de Clinic

Puede ser inconveniente tener la salida de Clinic intercalada con su código C convencional editado a mano. Afortunadamente, Clinic es configurable: puede almacenar en búfer su salida para imprimir más tarde (¡o antes!), O escribir su salida en un archivo separado. También puede agregar un prefijo o sufijo a cada línea del resultado generado por Clinic.

Si bien cambiar la salida de la Clínica de esta manera puede ser una bendición para la legibilidad, puede resultar en que el código de la Clínica utilice tipos antes de que se definan, o que su código intente utilizar el código generado por la Clínica antes de que se defina. Estos problemas pueden resolverse fácilmente reorganizando las declaraciones en su archivo o moviendo el código generado por Clinic a donde va. (Esta es la razón por la que el comportamiento predeterminado de Clinic es enviar todo al bloque actual; aunque muchas personas consideran que esto dificulta la legibilidad, nunca será necesario reorganizar su código para solucionar problemas de definición antes de su uso).

Comencemos por definir alguna terminología:

field

Un campo, en este contexto, es una subsección del resultado de la Clínica. Por ejemplo, el #define para la estructura PyMethodDef es un campo, llamado methoddef_define. La clínica tiene siete campos diferentes que puede generar por definición de función:

docstring_prototype
docstring_definition
methoddef_define
impl_prototype
parser_prototype
parser_definition
impl_definition

Todos los nombres tienen la forma "<a>_<b>", donde "<a>" es el objeto semántico representado (la función de análisis, la función impl, el docstring o la estructura methoddef) y "<b>" representa qué tipo de declaración es el campo. Los nombres de campo que terminan en "_prototype" representan declaraciones hacia adelante de esa cosa, sin el cuerpo/datos reales de la cosa; los nombres de campo que terminan en "_definition" representan la definición real de la cosa, con el cuerpo/datos de la cosa. ("methoddef" es especial, es el único que termina con "_define", lo que representa que es un preprocesador #define).

destination

Un destino es un lugar en el que la Clínica puede escribir resultados. Hay cinco destinos incorporados:

block

El destino predeterminado: impreso en la sección de salida del bloque Clínico actual.

buffer

Un búfer de texto donde puede guardar texto para más tarde. El texto enviado aquí se agrega al final de cualquier texto existente. Es un error dejar texto en el búfer cuando Clinic termina de procesar un archivo.

file

Un «archivo clínico» separado que Clinic creará automáticamente. El nombre de archivo elegido para el archivo es {basename}.clinic{extension}, donde a basename y extension se les asignó la salida de os.path.splitext() ejecutar en El archivo actual. (Ejemplo: el destino del file para _pickle.c se escribiría en _pickle.clinic.c.)

Importante: Al usar un destino **``file``, debe registrar el archivo generado!

two-pass

Un búfer como buffer. Sin embargo, un búfer de dos pasadas solo se puede volcar una vez, e imprime todo el texto que se le envía durante todo el procesamiento, incluso desde los bloques de la Clínica después del punto de descarga.

suppress

El texto se suprime — se tira.

Clinic define cinco nuevas directivas que le permiten reconfigurar su salida.

La primera nueva directiva es dump:

dump <destination>

Esto vuelca el contenido actual del destino nombrado en la salida del bloque actual y lo vacía. Esto solo funciona con destinos de búfer y de dos pasadas.

La segunda nueva directiva es output. La forma más básica de output es así:

output <field> <destination>

Esto le dice a la Clínica que envíe field a destination. output también admite un metadestino especial, llamado everything, que le dice a Clinic que envíe todos los campos a ese destination.

output tiene una serie de otras funciones:

output push
output pop
output preset <preset>

output push y output pop le permiten agregar y quitar configuraciones en una pila de configuración interna, para que pueda modificar temporalmente la configuración de salida, y luego restaurar fácilmente la configuración anterior. Simplemente presione antes de su cambio para guardar la configuración actual, luego haga estallar cuando desee restaurar la configuración anterior.

output preset configura la salida de Clinic en una de varias configuraciones preestablecidas incorporadas, de la siguiente manera:

block

Configuración inicial original de la clínica. Escribe todo inmediatamente después del bloque de entrada.

Suprime el parser_prototype y docstring_prototype, escribe todo lo demás en block.

file

Diseñado para escribir todo lo que pueda en el «archivo clínico». Luego, #include este archivo cerca de la parte superior de su archivo. Es posible que deba reorganizar su archivo para que esto funcione, aunque generalmente esto solo significa crear declaraciones hacia adelante para varias definiciones de typedef y PyTypeObject.

Suprima parser_prototype y docstring_prototype, escriba la impl_definition en block y escriba todo lo demás en file.

El nombre de archivo predeterminado es "{dirname}/clinic/{basename}.h".

buffer

Guarde la mayor parte del resultado de Clinic para escribirlo en su archivo cerca del final. Para los archivos Python que implementan módulos o tipos incorporado, se recomienda que descargue el búfer justo encima de las estructuras estáticas para su módulo o tipo incorporado; estos suelen estar muy cerca del final. El uso de buffer puede requerir incluso más edición que file, si su archivo tiene arreglos estáticos PyMethodDef definidos en el medio del archivo.

Suprima el parser_prototype, impl_prototype y docstring_prototype, escriba impl_definition en block y escriba todo lo demás en file.

two-pass

Similar al ajuste preestablecido de buffer, pero escribe declaraciones hacia adelante en el búfer de dos pasadas y definiciones en el buffer. Esto es similar al ajuste preestablecido de buffer, pero puede requerir menos edición que buffer. Vierta el búfer de dos pasadas cerca de la parte superior de su archivo y descargue el buffer cerca del final como lo haría cuando usa el ajuste preestablecido de buffer.

Suprime el impl_prototype, escribe impl_definition en block, escribe docstring_prototype, methoddef_define y parser_prototype en two-pass, escribe todo lo demás en buffer.

partial-buffer

Similar al ajuste preestablecido de buffer, pero escribe más cosas en block, solo escribe los trozos realmente grandes de código generado en buffer. Esto evita el problema de definición antes del uso de buffer por completo, con el pequeño costo de tener un poco más de material en la salida del bloque. Vierta el buffer cerca del final, tal como lo haría cuando usa el ajuste predeterminado de buffer.

Suprime el impl_prototype, escribe docstring_definition y parser_definition en buffer, escribe todo lo demás en block.

La tercera nueva directiva es destino:

destination <name> <command> [...]

Esto realiza una operación en el destino llamado name.

Hay dos subcomandos definidos: new y clear.

El subcomando new funciona así:

destination <name> new <type>

Esto crea un nuevo destino con el nombre <nombre> y escribe <tipo>.

Hay cinco tipos de destinos:

suppress

Tira el texto.

block

Escribe el texto en el bloque actual. Esto es lo que hizo Clinic originalmente.

buffer

Un búfer de texto simple, como el destino incorporado «búfer» anterior.

file

Un archivo de texto. El destino del archivo toma un argumento adicional, una plantilla para usar para construir el nombre de archivo, así:

destino <name> nuevo <type> <file_template>

La plantilla puede usar tres cadenas internamente que serán reemplazadas por bits del nombre del archivo:

{path}

La ruta completa al archivo, incluido el directorio y el nombre de archivo completo.

{dirname}

El nombre del directorio en el que se encuentra el archivo.

{basename}

Solo el nombre del archivo, sin incluir el directorio.

{basename_root}

Nombre de base con la extensión recortada (todo hasta pero sin incluir el último “.”).

{basename_extension}

El último “.” y todo lo que sigue. Si el nombre base no contiene un punto, esta será la cadena de caracteres vacía.

Si no hay puntos en el nombre del archivo, {basename} y {filename} son iguales, y {extension} está vacía. «{basename}{extension}» es siempre exactamente igual que «{filename}». «

two-pass

Un búfer de dos pasadas (two-pass), como el destino incorporado de «dos pasadas» anterior.

El subcomando clear funciona así:

destination <name> clear

Elimina todo el texto acumulado hasta este punto en el destino. (No sé para qué necesitarías esto, pero pensé que tal vez sería útil mientras alguien está experimentando).

La cuarta nueva directiva está set:

set line_prefix "string"
set line_suffix "string"

set le permite configurar dos variables internas en la Clínica. line_prefix es una cadena que se antepondrá a cada línea de salida de la Clínica; line_suffix es una cadena de caracteres que se agregará a cada línea de salida de la Clínica.

Ambos admiten dos cadenas de caracteres de formato:

{block comment start}

Se convierte en la cadena de caracteres /*, la secuencia de texto de inicio de comentario para archivos C.

{block comment end}

Se convierte en la cadena */, la secuencia de texto del comentario final para los archivos C.

La nueva directiva final es una que no debería necesitar usar directamente, llamada preserve:

preserve

Esto le dice a Clinic que el contenido actual de la salida debe mantenerse, sin modificaciones. La Clínica lo usa internamente cuando se descarga la salida en archivos de file; envolverlo en un bloque Clinic permite que Clinic use su funcionalidad de suma de comprobación existente para garantizar que el archivo no se modificó a mano antes de sobrescribirlo.

El truco #ifdef

Si está convirtiendo una función que no está disponible en todas las plataformas, hay un truco que puede usar para hacer la vida un poco más fácil. El código existente probablemente se ve así:

#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

Y luego, en la estructura PyMethodDef en la parte inferior, el código existente tendrá:

#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */

En este escenario, debe encerrar el cuerpo de su función impl dentro de #ifdef, así:

#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

Luego, elimine esas tres líneas de la estructura PyMethodDef, reemplazándolas con la macro Argument Clinic generada:

MODULE_FUNCTIONNAME_METHODDEF

(Puede encontrar el nombre real de esta macro dentro del código generado. O puede calcularlo usted mismo: es el nombre de su función tal como se define en la primera línea de su bloque, pero con puntos cambiados a guiones bajos, mayúsculas y "_METHODDEF" agregado al final.)

Quizás se esté preguntando: ¿qué pasa si HAVE_FUNCTIONNAME no está definido? ¡La macro MODULE_FUNCTIONNAME_METHODDEF tampoco se definirá!

Aquí es donde Argument Clinic se vuelve muy inteligente. De hecho, detecta que el bloqueo de Argument Clinic podría estar desactivado por el #ifdef. Cuando eso sucede, genera un pequeño código adicional que se ve así:

#ifndef MODULE_FUNCTIONNAME_METHODDEF
    #define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */

Eso significa que la macro siempre funciona. Si la función está definida, se convierte en la estructura correcta, incluida la coma al final. Si la función no está definida, esto se convierte en nada.

Sin embargo, esto causa un problema delicado: ¿dónde debería poner Argument Clinic este código adicional cuando se usa el ajuste preestablecido de salida «bloque»? No puede entrar en el bloque de salida, porque podría desactivarse con #ifdef. (¡Ese es todo el punto!)

En esta situación, Argument Clinic escribe el código adicional en el destino del «búfer». Esto puede significar que recibe una queja de Argument Clinic:

Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.

Cuando esto suceda, simplemente abra su archivo, busque el bloque dump buffer que Argument Clinic agregó a su archivo (estará en la parte inferior), luego muévalo arriba de la estructura PyMethodDef donde esa macro se utiliza.

Usando Argument Clinic en archivos Python

De hecho, es posible utilizar Argument Clinic para preprocesar archivos Python. Por supuesto, no tiene sentido usar bloques de Argument Clinic, ya que la salida no tendría ningún sentido para el intérprete de Python. ¡Pero usar Argument Clinic para ejecutar bloques de Python le permite usar Python como un preprocesador de Python!

Dado que los comentarios de Python son diferentes de los comentarios de C, los bloques de Argument Clinic incrustados en archivos de Python tienen un aspecto ligeramente diferente. Se ven así:

#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/