*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!

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

* 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()".

* 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 *sí* 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.

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

     /*[clinic input]
     [clinic start generated code]*/

* 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]*/

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

* 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]*/

* 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]*/

* 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]*/

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

* 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]*/

* 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]*/

* 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"

* 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.

* 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;

         ...

* ¿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 */
     };

* 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:...]*/
