Docs: Remove the numbered steps from the Argument Clinic tutorial (#107203)

Instead, order the tutorial as one body of prose, making it easier to
align the tutorial according to Diátaxis principles.
This commit is contained in:
Erlend E. Aasland 2023-07-26 22:54:25 +02:00 committed by GitHub
parent 5aa6964a5c
commit 592395577c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 333 additions and 337 deletions

View File

@ -167,23 +167,23 @@ convert a function to work with it. Here, then, are the bare
minimum steps you'd need to follow to convert a function to minimum steps you'd need to follow to convert a function to
work with Argument Clinic. Note that for code you plan to work with Argument Clinic. Note that for code you plan to
check in to CPython, you really should take the conversion farther, check in to CPython, you really should take the conversion farther,
using some of the advanced concepts you'll see later on in using some of the :ref:`advanced concepts <clinic-howtos>`
the document (like "return converters" and "self converters"). you'll see later on in the document,
like :ref:`clinic-howto-return-converters`
and :ref:`clinic-howto-self-converter`.
But we'll keep it simple for this walkthrough so you can learn. But we'll keep it simple for this walkthrough so you can learn.
Let's dive in! First, make sure you're working with a freshly updated checkout
0. Make sure you're working with a freshly updated checkout
of the CPython trunk. of the CPython trunk.
1. Find a Python builtin that calls either :c:func:`PyArg_ParseTuple` Next, find a Python builtin that calls either :c:func:`PyArg_ParseTuple`
or :c:func:`PyArg_ParseTupleAndKeywords`, and hasn't been converted or :c:func:`PyArg_ParseTupleAndKeywords`, and hasn't been converted
to work with Argument Clinic yet. to work with Argument Clinic yet.
For my example I'm using For this tutorial, we'll be using
:py:meth:`_pickle.Pickler.dump <pickle.Pickler.dump>`. :py:meth:`_pickle.Pickler.dump <pickle.Pickler.dump>`.
2. If the call to the :c:func:`!PyArg_Parse*` function uses any of the If the call to the :c:func:`!PyArg_Parse*` function uses any of the
following format units: following format units...:
.. code-block:: none .. code-block:: none
@ -194,10 +194,9 @@ Let's dive in!
et et
et# et#
or if it has multiple calls to :c:func:`PyArg_ParseTuple`, ... or if it has multiple calls to :c:func:`PyArg_ParseTuple`,
you should choose a different function. Argument Clinic *does* you should choose a different function.
support all of these scenarios. But these are advanced (See :ref:`clinic-howto-advanced-converters` for those scenarios.)
topics—let's do something simpler for your first function.
Also, if the function has multiple calls to :c:func:`!PyArg_ParseTuple` Also, if the function has multiple calls to :c:func:`!PyArg_ParseTuple`
or :c:func:`PyArg_ParseTupleAndKeywords` where it supports different or :c:func:`PyArg_ParseTupleAndKeywords` where it supports different
@ -206,45 +205,48 @@ Let's dive in!
isn't suitable for conversion to Argument Clinic. Argument Clinic isn't suitable for conversion to Argument Clinic. Argument Clinic
doesn't support generic functions or polymorphic parameters. doesn't support generic functions or polymorphic parameters.
3. Add the following boilerplate above the function, creating our block:: Next, add the following boilerplate above the function,
creating our input block::
/*[clinic input] /*[clinic input]
[clinic start generated code]*/ [clinic start generated code]*/
4. Cut the docstring and paste it in between the ``[clinic]`` lines, Cut the docstring and paste it in between the ``[clinic]`` lines,
removing all the junk that makes it a properly quoted C string. removing all the junk that makes it a properly quoted C string.
When you're done you should have just the text, based at the left When you're done you should have just the text, based at the left
margin, with no line wider than 80 characters. margin, with no line wider than 80 characters.
(Argument Clinic will preserve indents inside the docstring.) Argument Clinic will preserve indents inside the docstring.
If the old docstring had a first line that looked like a function If the old docstring had a first line that looked like a function
signature, throw that line away. (The docstring doesn't need it signature, throw that line away; The docstring doesn't need it anymore ---
anymore—when you use :py:func:`help` on your builtin in the future, when you use :py:func:`help` on your builtin in the future,
the first line will be built automatically based on the function's the first line will be built automatically based on the function's signature.
signature.)
Sample:: Example docstring summary line::
/*[clinic input] /*[clinic input]
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
5. If your docstring doesn't have a "summary" line, Argument Clinic will If your docstring doesn't have a "summary" line, Argument Clinic will
complain. So let's make sure it has one. The "summary" line should complain, so let's make sure it has one. The "summary" line should
be a paragraph consisting of a single 80-column line be a paragraph consisting of a single 80-column line
at the beginning of the docstring. at the beginning of the docstring.
(See :pep:`257` regarding docstring conventions.)
(Our example docstring consists solely of a summary line, so the sample Our example docstring consists solely of a summary line, so the sample
code doesn't have to change for this step.) code doesn't have to change for this step.
6. Above the docstring, enter the name of the function, followed Now, above the docstring, enter the name of the function, followed
by a blank line. This should be the Python name of the function, by a blank line. This should be the Python name of the function,
and should be the full dotted path and should be the full dotted path to the function ---
to the function—it should start with the name of the module, it should start with the name of the module,
include any sub-modules, and if the function is a method on include any sub-modules, and if the function is a method on
a class it should include the class name too. a class it should include the class name too.
Sample:: In our example, :mod:`!_pickle` is the module, :py:class:`!Pickler` is the class,
and :py:meth:`!dump` is the method, so the name becomes
:py:meth:`!_pickle.Pickler.dump`::
/*[clinic input] /*[clinic input]
_pickle.Pickler.dump _pickle.Pickler.dump
@ -252,13 +254,13 @@ Let's dive in!
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
7. If this is the first time that module or class has been used with Argument If this is the first time that module or class has been used with Argument
Clinic in this C file, Clinic in this C file,
you must declare the module and/or class. Proper Argument Clinic hygiene you must declare the module and/or class. Proper Argument Clinic hygiene
prefers declaring these in a separate block somewhere near the prefers declaring these in a separate block somewhere near the
top of the C file, in the same way that include files and statics go at top of the C file, in the same way that include files and statics go at
the top. (In our sample code we'll just show the two blocks next to the top.
each other.) In our sample code we'll just show the two blocks next to each other.
The name of the class and module should be the same as the one The name of the class and module should be the same as the one
seen by Python. Check the name defined in the :c:type:`PyModuleDef` seen by Python. Check the name defined in the :c:type:`PyModuleDef`
@ -266,9 +268,7 @@ Let's dive in!
When you declare a class, you must also specify two aspects of its type When you declare a class, you must also specify two aspects of its type
in C: the type declaration you'd use for a pointer to an instance of in C: the type declaration you'd use for a pointer to an instance of
this class, and a pointer to the :c:type:`!PyTypeObject` for this class. this class, and a pointer to the :c:type:`!PyTypeObject` for this class::
Sample::
/*[clinic input] /*[clinic input]
module _pickle module _pickle
@ -281,13 +281,9 @@ Let's dive in!
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
Declare each of the parameters to the function. Each parameter
8. Declare each of the parameters to the function. Each parameter
should get its own line. All the parameter lines should be should get its own line. All the parameter lines should be
indented from the function name and the docstring. indented from the function name and the docstring.
The general form of these parameter lines is as follows: The general form of these parameter lines is as follows:
.. code-block:: none .. code-block:: none
@ -302,29 +298,28 @@ Let's dive in!
name_of_parameter: converter = default_value name_of_parameter: converter = default_value
Argument Clinic's support for "default values" is quite sophisticated; Argument Clinic's support for "default values" is quite sophisticated;
please see :ref:`the section below on default values <default_values>` see :ref:`clinic-howto-default-values` for more information.
for more information.
Add a blank line below the parameters. Next, add a blank line below the parameters.
What's a "converter"? It establishes both the type What's a "converter"?
of the variable used in C, and the method to convert the Python It establishes both the type of the variable used in C,
value into a C value at runtime. and the method to convert the Python value into a C value at runtime.
For now you're going to use what's called a "legacy converter"—a For now you're going to use what's called a "legacy converter" ---
convenience syntax intended to make porting old code into Argument a convenience syntax intended to make porting old code into Argument
Clinic easier. Clinic easier.
For each parameter, copy the "format unit" for that For each parameter, copy the "format unit" for that
parameter from the :c:func:`PyArg_Parse` format argument and parameter from the :c:func:`PyArg_Parse` format argument and
specify *that* as its converter, as a quoted specify *that* as its converter, as a quoted string.
string. ("format unit" is the formal name for the one-to-three The "format unit" is the formal name for the one-to-three
character substring of the *format* parameter that tells character substring of the *format* parameter that tells
the argument parsing function what the type of the variable the argument parsing function what the type of the variable
is and how to convert it. For more on format units please is and how to convert it.
see :ref:`arg-parsing`.) For more on format units please see :ref:`arg-parsing`.
For multicharacter format units like ``z#``, use the For multicharacter format units like ``z#``,
entire two-or-three character string. use the entire two-or-three character string.
Sample:: Sample::
@ -341,31 +336,26 @@ Let's dive in!
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
9. If your function has ``|`` in the format string, meaning some If your function has ``|`` in the format string,
parameters have default values, you can ignore it. Argument meaning some parameters have default values, you can ignore it.
Clinic infers which parameters are optional based on whether Argument Clinic infers which parameters are optional
or not they have default values. based on whether or not they have default values.
If your function has ``$`` in the format string, meaning it If your function has ``$`` in the format string,
takes keyword-only arguments, specify ``*`` on a line by meaning it takes keyword-only arguments,
itself before the first keyword-only argument, indented the specify ``*`` on a line by itself before the first keyword-only argument,
same as the parameter lines. indented the same as the parameter lines.
(:py:meth:`!_pickle.Pickler.dump` has neither, so our sample is unchanged.) :py:meth:`!_pickle.Pickler.dump` has neither, so our sample is unchanged.
Next, if the existing C function calls :c:func:`PyArg_ParseTuple`
10. If the existing C function calls :c:func:`PyArg_ParseTuple`
(as opposed to :c:func:`PyArg_ParseTupleAndKeywords`), then all its (as opposed to :c:func:`PyArg_ParseTupleAndKeywords`), then all its
arguments are positional-only. arguments are positional-only.
To mark all parameters as positional-only in Argument Clinic, To mark parameters as positional-only in Argument Clinic,
add a ``/`` on a line by itself after the last parameter, add a ``/`` on a line by itself after the last positional-only parameter,
indented the same as the parameter lines. indented the same as the parameter lines.
Currently this is all-or-nothing; either all parameters are
positional-only, or none of them are. (In the future Argument
Clinic may relax this restriction.)
Sample:: Sample::
/*[clinic input] /*[clinic input]
@ -382,16 +372,17 @@ Let's dive in!
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
11. It's helpful to write a per-parameter docstring for each parameter. It can be helpful to write a per-parameter docstring for each parameter.
But per-parameter docstrings are optional; you can skip this step Since per-parameter docstrings are optional,
if you prefer. you can skip this step if you prefer.
Here's how to add a per-parameter docstring. The first line Nevertheless, here's how to add a per-parameter docstring.
of the per-parameter docstring must be indented further than the The first line of the per-parameter docstring
parameter definition. The left margin of this first line establishes must be indented further than the parameter definition.
the left margin for the whole per-parameter docstring; all the text The left margin of this first line establishes
you write will be outdented by this amount. You can write as much the left margin for the whole per-parameter docstring;
text as you like, across multiple lines if you wish. all the text you write will be outdented by this amount.
You can write as much text as you like, across multiple lines if you wish.
Sample:: Sample::
@ -410,10 +401,10 @@ Let's dive in!
Write a pickled representation of obj to the open file. Write a pickled representation of obj to the open file.
[clinic start generated code]*/ [clinic start generated code]*/
12. Save and close the file, then run ``Tools/clinic/clinic.py`` on Save and close the file, then run ``Tools/clinic/clinic.py`` on it.
it. With luck everything worked---your block now has output, and With luck everything worked---your block now has output,
a :file:`.c.h` file has been generated! Reopen the file in your and a :file:`.c.h` file has been generated!
text editor to see:: Reload the file in your text editor to see the generated code::
/*[clinic input] /*[clinic input]
_pickle.Pickler.dump _pickle.Pickler.dump
@ -429,9 +420,10 @@ Let's dive in!
_pickle_Pickler_dump(PicklerObject *self, PyObject *obj) _pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
/*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/ /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
Obviously, if Argument Clinic didn't produce any output, it's because Obviously, if Argument Clinic didn't produce any output,
it found an error in your input. Keep fixing your errors and retrying it's because it found an error in your input.
until Argument Clinic processes your file without complaint. Keep fixing your errors and retrying until Argument Clinic processes your file
without complaint.
For readability, most of the glue code has been generated to a :file:`.c.h` For readability, most of the glue code has been generated to a :file:`.c.h`
file. You'll need to include that in your original :file:`.c` file, file. You'll need to include that in your original :file:`.c` file,
@ -439,7 +431,7 @@ Let's dive in!
#include "clinic/_pickle.c.h" #include "clinic/_pickle.c.h"
13. Double-check that the argument-parsing code Argument Clinic generated Double-check that the argument-parsing code Argument Clinic generated
looks basically the same as the existing code. looks basically the same as the existing code.
First, ensure both places use the same argument-parsing function. First, ensure both places use the same argument-parsing function.
@ -450,22 +442,22 @@ Let's dive in!
Second, the format string passed in to :c:func:`!PyArg_ParseTuple` or Second, the format string passed in to :c:func:`!PyArg_ParseTuple` or
:c:func:`!PyArg_ParseTupleAndKeywords` should be *exactly* the same :c:func:`!PyArg_ParseTupleAndKeywords` should be *exactly* the same
as the hand-written one in the existing function, up to the colon as the hand-written one in the existing function,
or semi-colon. up to the colon or semi-colon.
(Argument Clinic always generates its format strings Argument Clinic always generates its format strings
with a ``:`` followed by the name of the function. If the with a ``:`` followed by the name of the function.
existing code's format string ends with ``;``, to provide If the existing code's format string ends with ``;``,
usage help, this change is harmless—don't worry about it.) to provide usage help, this change is harmless --- don't worry about it.
Third, for parameters whose format units require two arguments Third, for parameters whose format units require two arguments,
(like a length variable, or an encoding string, or a pointer like a length variable, an encoding string, or a pointer
to a conversion function), ensure that the second argument is to a conversion function, ensure that the second argument is
*exactly* the same between the two invocations. *exactly* the same between the two invocations.
Fourth, inside the output portion of the block you'll find a preprocessor Fourth, inside the output portion of the block,
macro defining the appropriate static :c:type:`PyMethodDef` structure for you'll find a preprocessor macro defining the appropriate static
this builtin:: :c:type:`PyMethodDef` structure for this builtin::
#define __PICKLE_PICKLER_DUMP_METHODDEF \ #define __PICKLE_PICKLER_DUMP_METHODDEF \
{"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__}, {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
@ -477,8 +469,7 @@ Let's dive in!
adjust your Argument Clinic function specification and rerun adjust your Argument Clinic function specification and rerun
``Tools/clinic/clinic.py`` until they *are* the same. ``Tools/clinic/clinic.py`` until they *are* the same.
Notice that the last line of its output is the declaration
14. Notice that the last line of its output is the declaration
of your "impl" function. This is where the builtin's implementation goes. of your "impl" function. This is where the builtin's implementation goes.
Delete the existing prototype of the function you're modifying, but leave Delete the existing prototype of the function you're modifying, but leave
the opening curly brace. Now delete its argument parsing code and the the opening curly brace. Now delete its argument parsing code and the
@ -486,17 +477,17 @@ Let's dive in!
Notice how the Python arguments are now arguments to this impl function; Notice how the Python arguments are now arguments to this impl function;
if the implementation used different names for these variables, fix it. if the implementation used different names for these variables, fix it.
Let's reiterate, just because it's kind of weird. Your code should now Let's reiterate, just because it's kind of weird.
look like this:: Your code should now look like this::
static return_type static return_type
your_function_impl(...) your_function_impl(...)
/*[clinic end generated code: checksum=...]*/ /*[clinic end generated code: input=..., output=...]*/
{ {
... ...
Argument Clinic generated the checksum line and the function prototype just Argument Clinic generated the checksum line and the function prototype just
above it. You should write the opening (and closing) curly braces for the above it. You should write the opening and closing curly braces for the
function, and the implementation inside. function, and the implementation inside.
Sample:: Sample::
@ -535,19 +526,20 @@ Let's dive in!
return NULL; return NULL;
} }
if (_Pickler_ClearBuffer(self) < 0) if (_Pickler_ClearBuffer(self) < 0) {
return NULL; return NULL;
}
... ...
15. Remember the macro with the :c:type:`PyMethodDef` structure for this Remember the macro with the :c:type:`PyMethodDef` structure for this function?
function? Find the existing :c:type:`!PyMethodDef` structure for this Find the existing :c:type:`!PyMethodDef` structure for this
function and replace it with a reference to the macro. (If the builtin function and replace it with a reference to the macro. If the builtin
is at module scope, this will probably be very near the end of the file; is at module scope, this will probably be very near the end of the file;
if the builtin is a class method, this will probably be below but relatively if the builtin is a class method, this will probably be below but relatively
near to the implementation.) near to the implementation.
Note that the body of the macro contains a trailing comma. So when you Note that the body of the macro contains a trailing comma; when you
replace the existing static :c:type:`!PyMethodDef` structure with the macro, replace the existing static :c:type:`!PyMethodDef` structure with the macro,
*don't* add a comma to the end. *don't* add a comma to the end.
@ -559,20 +551,17 @@ Let's dive in!
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
Argument Clinic may generate new instances of ``_Py_ID``. For example::
16. Argument Clinic may generate new instances of ``_Py_ID``. For example::
&_Py_ID(new_unique_py_id) &_Py_ID(new_unique_py_id)
If it does, you'll have to run ``make regen-global-objects`` If it does, you'll have to run ``make regen-global-objects``
to regenerate the list of precompiled identifiers at this point. to regenerate the list of precompiled identifiers at this point.
Finally, compile, then run the relevant portions of the regression-test suite.
17. Compile, then run the relevant portions of the regression-test suite.
This change should not introduce any new compile-time warnings or errors, This change should not introduce any new compile-time warnings or errors,
and there should be no externally visible change to Python's behavior. and there should be no externally visible change to Python's behavior,
except for one difference: :py:func:`inspect.signature` run on your function
Well, except for one difference: :py:func:`inspect.signature` run on your function
should now provide a valid signature! should now provide a valid signature!
Congratulations, you've ported your first function to work with Argument Clinic! Congratulations, you've ported your first function to work with Argument Clinic!
@ -913,6 +902,8 @@ you *must* not call :c:func:`PyBuffer_Release` on the provided buffer.
Argument Clinic generates code that does it for you (in the parsing function). Argument Clinic generates code that does it for you (in the parsing function).
.. _clinic-howto-advanced-converters:
How to use advanced converters How to use advanced converters
------------------------------ ------------------------------
@ -943,6 +934,7 @@ This restriction doesn't seem unreasonable; CPython itself always passes in stat
hard-coded encoding strings for parameters whose format units start with ``e``. hard-coded encoding strings for parameters whose format units start with ``e``.
.. _clinic-howto-default-values:
.. _default_values: .. _default_values:
How to assign default values to parameter How to assign default values to parameter
@ -1053,6 +1045,8 @@ you're not permitted to use:
* Tuple/list/set/dict literals. * Tuple/list/set/dict literals.
.. _clinic-howto-return-converters:
How to use return converters How to use return converters
---------------------------- ----------------------------
@ -1195,6 +1189,8 @@ variable to the C code::
/*[python checksum:...]*/ /*[python checksum:...]*/
.. _clinic-howto-self-converter:
How to use the "self converter" How to use the "self converter"
------------------------------- -------------------------------