mirror of
https://github.com/python/cpython.git
synced 2025-11-03 11:23:31 +00:00
Argument Clinic: fixed test suite, improved howto.
This commit is contained in:
parent
5ea97506a2
commit
6d2ea21337
3 changed files with 173 additions and 101 deletions
|
|
@ -14,21 +14,20 @@ Argument Clinic How-To
|
||||||
function to work with Argument Clinic, and then introduces
|
function to work with Argument Clinic, and then introduces
|
||||||
some advanced topics on Argument Clinic usage.
|
some advanced topics on Argument Clinic usage.
|
||||||
|
|
||||||
Argument Clinic is currently considered an internal
|
Currently Argument Clinic is considered internal-only
|
||||||
tool for the CPython code tree. Its use is not supported
|
for CPython. Its use is not supported for files outside
|
||||||
for files outside the CPython code tree, and no guarantees
|
CPython, and no guarantees are made regarding backwards
|
||||||
are made regarding backwards compatibility for future
|
compatibility for future versions. In other words: if you
|
||||||
versions. In other words: if you maintain an external C
|
maintain an external C extension for CPython, you're welcome
|
||||||
extension for CPython, you're welcome to experiment with
|
to experiment with Argument Clinic in your own code. But the
|
||||||
Argument Clinic in your own code. But the version of Argument
|
version of Argument Clinic that ships with CPython 3.5 *could*
|
||||||
Clinic that ships with CPython 3.5 *could* be totally
|
be totally incompatible and break all your code.
|
||||||
incompatible and break all your code.
|
|
||||||
|
|
||||||
========================
|
========================
|
||||||
Basic Concepts And Usage
|
Basic Concepts And Usage
|
||||||
========================
|
========================
|
||||||
|
|
||||||
Argument Clinic ships with CPython. You can find it in ``Tools/clinic/clinic.py``.
|
Argument Clinic ships with CPython; you'll find it in ``Tools/clinic/clinic.py``.
|
||||||
If you run that script, specifying a C file as an argument::
|
If you run that script, specifying a C file as an argument::
|
||||||
|
|
||||||
% python3 Tools/clinic/clinic.py foo.c
|
% python3 Tools/clinic/clinic.py foo.c
|
||||||
|
|
@ -45,13 +44,12 @@ like this::
|
||||||
|
|
||||||
Everything in between these two lines is input for Argument Clinic.
|
Everything in between these two lines is input for Argument Clinic.
|
||||||
All of these lines, including the beginning and ending comment
|
All of these lines, including the beginning and ending comment
|
||||||
lines, are collectively called an Argument Clinic "input block",
|
lines, are collectively called an Argument Clinic "block".
|
||||||
or "block" for short.
|
|
||||||
|
|
||||||
When Argument Clinic parses one of these blocks, it
|
When Argument Clinic parses one of these blocks, it
|
||||||
generates output. This output is rewritten into the C file
|
generates output. This output is rewritten into the C file
|
||||||
immediately after the block, followed by a comment containing a checksum.
|
immediately after the block, followed by a comment containing a checksum.
|
||||||
The resulting Argument Clinic block looks like this::
|
The Argument Clinic block now looks like this::
|
||||||
|
|
||||||
/*[clinic]
|
/*[clinic]
|
||||||
... clinic input goes here ...
|
... clinic input goes here ...
|
||||||
|
|
@ -65,7 +63,8 @@ line. However, if the input hasn't changed, the output won't change either.
|
||||||
|
|
||||||
You should never modify the output portion of an Argument Clinic block. Instead,
|
You should never modify the output portion of an Argument Clinic block. Instead,
|
||||||
change the input until it produces the output you want. (That's the purpose of the
|
change the input until it produces the output you want. (That's the purpose of the
|
||||||
checksum--to detect and warn you in case someone accidentally modifies the output.)
|
checksum--to detect if someone changed the output, as these edits would be lost
|
||||||
|
the next time Argument Clinic writes out fresh output.)
|
||||||
|
|
||||||
For the sake of clarity, here's the terminology we'll use with Argument Clinic:
|
For the sake of clarity, here's the terminology we'll use with Argument Clinic:
|
||||||
|
|
||||||
|
|
@ -87,10 +86,12 @@ Converting Your First Function
|
||||||
The best way to get a sense of how Argument Clinic works is to
|
The best way to get a sense of how Argument Clinic works is to
|
||||||
convert a function to work with it. Let's dive in!
|
convert a function to work with it. Let's dive in!
|
||||||
|
|
||||||
0. Make sure you're working with a freshly updated trunk.
|
0. Make sure you're working with a freshly updated checkout
|
||||||
|
of the CPython trunk.
|
||||||
|
|
||||||
1. Find a Python builtin that calls either ``PyArg_ParseTuple()``
|
1. Find a Python builtin that calls either :c:func:`PyArg_ParseTuple`
|
||||||
or ``PyArg_ParseTupleAndKeywords()``, and hasn't been converted yet.
|
or :c:func:`PyArg_ParseTupleAndKeywords`, and hasn't been converted
|
||||||
|
to work with Argument Clinic yet.
|
||||||
For my example I'm using ``pickle.Pickler.dump()``.
|
For my example I'm using ``pickle.Pickler.dump()``.
|
||||||
|
|
||||||
2. If the call to the ``PyArg_Parse`` function uses any of the
|
2. If the call to the ``PyArg_Parse`` function uses any of the
|
||||||
|
|
@ -103,7 +104,7 @@ convert a function to work with it. Let's dive in!
|
||||||
et
|
et
|
||||||
et#
|
et#
|
||||||
|
|
||||||
or if it has multiple calls to ``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. Argument Clinic *does*
|
||||||
support all of these scenarios. But these are advanced
|
support all of these scenarios. But these are advanced
|
||||||
topics--let's do something simpler for your first function.
|
topics--let's do something simpler for your first function.
|
||||||
|
|
@ -130,7 +131,7 @@ convert a function to work with it. Let's dive in!
|
||||||
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.
|
||||||
|
|
||||||
(Our docstring consists solely of the 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
|
6. Above the docstring, enter the name of the function, followed
|
||||||
|
|
@ -198,7 +199,8 @@ convert a function to work with it. Let's dive in!
|
||||||
string. ("format unit" is the formal name for the one-to-three
|
string. ("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.)
|
is and how to convert it. For more on format units please
|
||||||
|
see :ref:`arg-parsing`.)
|
||||||
|
|
||||||
For multicharacter format units like ``z#``, use the
|
For multicharacter format units like ``z#``, use the
|
||||||
entire two-or-three character string.
|
entire two-or-three character string.
|
||||||
|
|
@ -231,14 +233,18 @@ convert a function to work with it. Let's dive in!
|
||||||
(``pickle.Pickler.dump`` has neither, so our sample is unchanged.)
|
(``pickle.Pickler.dump`` has neither, so our sample is unchanged.)
|
||||||
|
|
||||||
|
|
||||||
10. If the existing C function uses ``PyArg_ParseTuple()``
|
10. If the existing C function calls :c:func:`PyArg_ParseTuple`
|
||||||
(instead of ``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 all 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 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]
|
/*[clinic]
|
||||||
|
|
@ -255,16 +261,16 @@ convert a function to work with it. 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]*/
|
[clinic]*/
|
||||||
|
|
||||||
11. It's helpful to write a per-parameter docstring, indented
|
11. It's helpful to write a per-parameter docstring for each parameter.
|
||||||
another level past the parameter declaration. But per-parameter
|
But per-parameter docstrings are optional; you can skip this step
|
||||||
docstrings are optional; you can skip this step if you prefer.
|
if you prefer.
|
||||||
|
|
||||||
Here's how per-parameter docstrings work. The first line
|
Here's how to add a per-parameter docstring. The first line
|
||||||
of the per-parameter docstring must be indented further than the
|
of the per-parameter docstring must be indented further than the
|
||||||
parameter definition. This left margin establishes the left margin
|
parameter definition. The left margin of this first line establishes
|
||||||
for the whole per-parameter docstring; all the text you write will
|
the left margin for the whole per-parameter docstring; all the text
|
||||||
be outdented by this amount. You can write as much as you like,
|
you write will be outdented by this amount. You can write as much
|
||||||
across multiple lines if you wish.
|
text as you like, across multiple lines if you wish.
|
||||||
|
|
||||||
Sample::
|
Sample::
|
||||||
|
|
||||||
|
|
@ -311,28 +317,47 @@ convert a function to work with it. Let's dive in!
|
||||||
pickle_Pickler_dump_impl(PyObject *self, PyObject *obj)
|
pickle_Pickler_dump_impl(PyObject *self, PyObject *obj)
|
||||||
/*[clinic checksum: 3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
|
/*[clinic checksum: 3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
|
||||||
|
|
||||||
|
Obviously, if Argument Clinic didn't produce any output, it's because
|
||||||
|
it found an error in your input. Keep fixing your errors and retrying
|
||||||
|
until Argument Clinic processes your file without complaint.
|
||||||
|
|
||||||
13. Double-check that the argument-parsing code Argument Clinic generated
|
13. 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.
|
||||||
The existing code must call either
|
The existing code must call either
|
||||||
``PyArg_ParseTuple()`` or ``PyArg_ParseTupleAndKeywords()``;
|
:c:func:`PyArg_ParseTuple` or :c:func:`PyArg_ParseTupleAndKeywords`;
|
||||||
ensure that the code generated by Argument Clinic calls the
|
ensure that the code generated by Argument Clinic calls the
|
||||||
same function.
|
*exact* same function.
|
||||||
|
|
||||||
Second, the format string passed in to ``PyArg_ParseTuple()`` or
|
Second, the format string passed in to :c:func:`PyArg_ParseTuple` or
|
||||||
``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.
|
as the hand-written one in the existing function, up to the colon
|
||||||
|
or semi-colon.
|
||||||
|
|
||||||
Well, there's one way that Argument Clinic's output is permitted
|
(Argument Clinic always generates its format strings
|
||||||
to be different. Argument Clinic always generates a format string
|
with a ``:`` followed by the name of the function. If the
|
||||||
ending with ``:`` followed by the name of the function. If the
|
existing code's format string ends with ``;``, to provide
|
||||||
format string originally ended with ``;`` (to specify usage help),
|
usage help, this change is harmless--don't worry about it.)
|
||||||
this is harmless--don't worry about this difference.
|
|
||||||
|
|
||||||
Apart from that, if either of these things differ in *any way*,
|
Third, for parameters whose format units require two arguments
|
||||||
fix your input to Argument Clinic and rerun ``Tools/clinic/clinic.py``
|
(like a length variable, or an encoding string, or a pointer
|
||||||
until they are the same.
|
to a conversion function), ensure that the second argument is
|
||||||
|
*exactly* the same between the two invocations.
|
||||||
|
|
||||||
|
Fourth, inside the output portion of the block you'll find a preprocessor
|
||||||
|
macro defining the appropriate static :c:type:`PyMethodDef` structure for
|
||||||
|
this builtin::
|
||||||
|
|
||||||
|
#define _PICKLE_PICKLER_DUMP_METHODDEF \
|
||||||
|
{"dump", (PyCFunction)_pickle_Pickler_dump, METH_O, _pickle_Pickler_dump__doc__},
|
||||||
|
|
||||||
|
This static structure should be *exactly* the same as the existing static
|
||||||
|
:c:type:`PyMethodDef` structure for this builtin.
|
||||||
|
|
||||||
|
If any of these items differ in *any way*,
|
||||||
|
adjust your Argument Clinic function specification and rerun
|
||||||
|
``Tools/clinic/clinic.py`` until they *are* the same.
|
||||||
|
|
||||||
|
|
||||||
14. Notice that the last line of its output is the declaration
|
14. Notice that the last line of its output is the declaration
|
||||||
|
|
@ -342,8 +367,19 @@ convert a function to work with it. Let's dive in!
|
||||||
declarations of all the variables it dumps the arguments into.
|
declarations of all the variables it dumps the arguments into.
|
||||||
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.
|
||||||
The result should be a function that handles just the implementation
|
|
||||||
of the Python function without any argument-parsing code.
|
Let's reiterate, just because it's kind of weird. Your code should now
|
||||||
|
look like this::
|
||||||
|
|
||||||
|
static return_type
|
||||||
|
your_function_impl(...)
|
||||||
|
/*[clinic checksum: ...]*/
|
||||||
|
{
|
||||||
|
...
|
||||||
|
|
||||||
|
Argument Clinic generated the checksum line and the function prototype just
|
||||||
|
above it. You should write the opening (and closing) curly braces for the
|
||||||
|
function, and the implementation inside.
|
||||||
|
|
||||||
Sample::
|
Sample::
|
||||||
|
|
||||||
|
|
@ -386,7 +422,27 @@ convert a function to work with it. Let's dive in!
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
15. Compile and run the relevant portions of the regression-test suite.
|
15. Remember the macro with the :c:type:`PyMethodDef` structure for this
|
||||||
|
function? Find the existing :c:type:`PyMethodDef` structure for this
|
||||||
|
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;
|
||||||
|
if the builtin is a class method, this will probably be below but relatively
|
||||||
|
near to the implementation.)
|
||||||
|
|
||||||
|
Note that the body of the macro contains a trailing comma. So when you
|
||||||
|
replace the existing static :c:type:`PyMethodDef` structure with the macro,
|
||||||
|
*don't* add a comma to the end.
|
||||||
|
|
||||||
|
Sample::
|
||||||
|
|
||||||
|
static struct PyMethodDef Pickler_methods[] = {
|
||||||
|
_PICKLE_PICKLER_DUMP_METHODDEF
|
||||||
|
_PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
|
||||||
|
{NULL, NULL} /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
16. 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.
|
||||||
|
|
||||||
|
|
@ -405,11 +461,11 @@ Renaming the C functions generated by Argument Clinic
|
||||||
|
|
||||||
Argument Clinic automatically names the functions it generates for you.
|
Argument Clinic automatically names the functions it generates for you.
|
||||||
Occasionally this may cause a problem, if the generated name collides with
|
Occasionally this may cause a problem, if the generated name collides with
|
||||||
the name of an existing C function. There's an easy solution: you can explicitly
|
the name of an existing C function. There's an easy solution: override the names
|
||||||
specify the base name to use for the C functions. Just add the keyword ``"as"``
|
used for the C functions. Just add the keyword ``"as"``
|
||||||
to your function declaration line, followed by the function name you wish to use.
|
to your function declaration line, followed by the function name you wish to use.
|
||||||
Argument Clinic will use the function name you use for the base (generated) function,
|
Argument Clinic will use that function name for the base (generated) function,
|
||||||
and then add ``"_impl"`` to the end for the name of the impl function.
|
then add ``"_impl"`` to the end and use that for the name of the impl function.
|
||||||
|
|
||||||
For example, if we wanted to rename the C function names generated for
|
For example, if we wanted to rename the C function names generated for
|
||||||
``pickle.Pickler.dump``, it'd look like this::
|
``pickle.Pickler.dump``, it'd look like this::
|
||||||
|
|
@ -420,7 +476,7 @@ For example, if we wanted to rename the C function names generated for
|
||||||
...
|
...
|
||||||
|
|
||||||
The base function would now be named ``pickler_dumper()``,
|
The base function would now be named ``pickler_dumper()``,
|
||||||
and the impl function would be named ``pickler_dumper_impl()``.
|
and the impl function would now be named ``pickler_dumper_impl()``.
|
||||||
|
|
||||||
|
|
||||||
Optional Groups
|
Optional Groups
|
||||||
|
|
@ -428,15 +484,15 @@ Optional Groups
|
||||||
|
|
||||||
Some legacy functions have a tricky approach to parsing their arguments:
|
Some legacy functions have a tricky approach to parsing their arguments:
|
||||||
they count the number of positional arguments, then use a ``switch`` statement
|
they count the number of positional arguments, then use a ``switch`` statement
|
||||||
to call one of several different ``PyArg_ParseTuple()`` calls depending on
|
to call one of several different :c:func:`PyArg_ParseTuple` calls depending on
|
||||||
how many positional arguments there are. (These functions cannot accept
|
how many positional arguments there are. (These functions cannot accept
|
||||||
keyword-only arguments.) This approach was used to simulate optional
|
keyword-only arguments.) This approach was used to simulate optional
|
||||||
arguments back before ``PyArg_ParseTupleAndKeywords()`` was created.
|
arguments back before :c:func:`PyArg_ParseTupleAndKeywords` was created.
|
||||||
|
|
||||||
Functions using this approach can often be converted to
|
While functions using this approach can often be converted to
|
||||||
use ``PyArg_ParseTupleAndKeywords()``, optional arguments, and default values.
|
use :c:func:`PyArg_ParseTupleAndKeywords`, optional arguments, and default values,
|
||||||
But it's not always possible, because some of these legacy functions have
|
it's not always possible. Some of these legacy functions have
|
||||||
behaviors ``PyArg_ParseTupleAndKeywords()`` can't directly support.
|
behaviors :c:func:`PyArg_ParseTupleAndKeywords` doesn't directly support.
|
||||||
The most obvious example is the builtin function ``range()``, which has
|
The most obvious example is the builtin function ``range()``, which has
|
||||||
an optional argument on the *left* side of its required argument!
|
an optional argument on the *left* side of its required argument!
|
||||||
Another example is ``curses.window.addch()``, which has a group of two
|
Another example is ``curses.window.addch()``, which has a group of two
|
||||||
|
|
@ -445,16 +501,17 @@ called ``x`` and ``y``; if you call the function passing in ``x``,
|
||||||
you must also pass in ``y``--and if you don't pass in ``x`` you may not
|
you must also pass in ``y``--and if you don't pass in ``x`` you may not
|
||||||
pass in ``y`` either.)
|
pass in ``y`` either.)
|
||||||
|
|
||||||
For the sake of backwards compatibility, Argument Clinic supports this
|
In any case, the goal of Argument Clinic is to support argument parsing
|
||||||
alternate approach to parsing, using what are called *optional groups*.
|
for all existing CPython builtins without changing their semantics.
|
||||||
Optional groups are groups of arguments that can only be specified together.
|
Therefore Argument Clinic supports
|
||||||
|
this alternate approach to parsing, using what are called *optional groups*.
|
||||||
|
Optional groups are groups of arguments that must all be passed in together.
|
||||||
They can be to the left or the right of the required arguments. They
|
They can be to the left or the right of the required arguments. They
|
||||||
can *only* be used with positional-only parameters.
|
can *only* be used with positional-only parameters.
|
||||||
|
|
||||||
To specify an optional group, add a ``[`` on a line by itself before
|
To specify an optional group, add a ``[`` on a line by itself before
|
||||||
the parameters you wish to be
|
the parameters you wish to group together, and a ``]`` on a line by itself
|
||||||
in a group together, and a ``]`` on a line by itself after the
|
after these parameters. As an example, here's how ``curses.window.addch``
|
||||||
parameters. As an example, here's how ``curses.window.addch``
|
|
||||||
uses optional groups to make the first two parameters and the last
|
uses optional groups to make the first two parameters and the last
|
||||||
parameter optional::
|
parameter optional::
|
||||||
|
|
||||||
|
|
@ -484,8 +541,8 @@ parameter optional::
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
* For every optional group, one additional parameter will be passed into the
|
* For every optional group, one additional parameter will be passed into the
|
||||||
impl function representing the group. The parameter will be an int, and it will
|
impl function representing the group. The parameter will be an int named
|
||||||
be named ``group_{direction}_{number}``,
|
``group_{direction}_{number}``,
|
||||||
where ``{direction}`` is either ``right`` or ``left`` depending on whether the group
|
where ``{direction}`` is either ``right`` or ``left`` depending on whether the group
|
||||||
is before or after the required parameters, and ``{number}`` is a monotonically
|
is before or after the required parameters, and ``{number}`` is a monotonically
|
||||||
increasing number (starting at 1) indicating how far away the group is from
|
increasing number (starting at 1) indicating how far away the group is from
|
||||||
|
|
@ -495,11 +552,13 @@ Notes:
|
||||||
in this invocation.)
|
in this invocation.)
|
||||||
|
|
||||||
* If there are no required arguments, the optional groups will behave
|
* If there are no required arguments, the optional groups will behave
|
||||||
as if they are to the right of the required arguments.
|
as if they're to the right of the required arguments.
|
||||||
|
|
||||||
* In the case of ambiguity, the argument parsing code
|
* In the case of ambiguity, the argument parsing code
|
||||||
favors parameters on the left (before the required parameters).
|
favors parameters on the left (before the required parameters).
|
||||||
|
|
||||||
|
* Optional groups can only contain positional-only parameters.
|
||||||
|
|
||||||
* Optional groups are *only* intended for legacy code. Please do not
|
* Optional groups are *only* intended for legacy code. Please do not
|
||||||
use optional groups for new code.
|
use optional groups for new code.
|
||||||
|
|
||||||
|
|
@ -509,7 +568,7 @@ Using real Argument Clinic converters, instead of "legacy converters"
|
||||||
|
|
||||||
To save time, and to minimize how much you need to learn
|
To save time, and to minimize how much you need to learn
|
||||||
to achieve your first port to Argument Clinic, the walkthrough above tells
|
to achieve your first port to Argument Clinic, the walkthrough above tells
|
||||||
you to use the "legacy converters". "Legacy converters" are a convenience,
|
you to use "legacy converters". "Legacy converters" are a convenience,
|
||||||
designed explicitly to make porting existing code to Argument Clinic
|
designed explicitly to make porting existing code to Argument Clinic
|
||||||
easier. And to be clear, their use is entirely acceptable when porting
|
easier. And to be clear, their use is entirely acceptable when porting
|
||||||
code for Python 3.4.
|
code for Python 3.4.
|
||||||
|
|
@ -523,18 +582,19 @@ reasons:
|
||||||
because they require arguments, and the legacy converter syntax doesn't
|
because they require arguments, and the legacy converter syntax doesn't
|
||||||
support specifying arguments.
|
support specifying arguments.
|
||||||
* In the future we may have a new argument parsing library that isn't
|
* In the future we may have a new argument parsing library that isn't
|
||||||
restricted to what ``PyArg_ParseTuple()`` supports.
|
restricted to what :c:func:`PyArg_ParseTuple` supports; this flexibility
|
||||||
|
won't be available to parameters using legacy converters.
|
||||||
|
|
||||||
So if you want
|
Therefore, if you don't mind a little extra effort, you should consider
|
||||||
to go that extra effort, you should consider using normal
|
using normal converters instead of legacy converters.
|
||||||
converters instead of the legacy converters.
|
|
||||||
|
|
||||||
In a nutshell, the syntax for Argument Clinic (non-legacy) converters
|
In a nutshell, the syntax for Argument Clinic (non-legacy) converters
|
||||||
looks like a Python function call. However, if there are no explicit
|
looks like a Python function call. However, if there are no explicit
|
||||||
arguments to the function (all functions take their default values),
|
arguments to the function (all functions take their default values),
|
||||||
you may omit the parentheses. Thus ``bool`` and ``bool()`` are exactly
|
you may omit the parentheses. Thus ``bool`` and ``bool()`` are exactly
|
||||||
the same. All parameters to Argument Clinic converters are keyword-only.
|
the same converters.
|
||||||
|
|
||||||
|
All arguments to Argument Clinic converters are keyword-only.
|
||||||
All Argument Clinic converters accept the following arguments:
|
All Argument Clinic converters accept the following arguments:
|
||||||
|
|
||||||
``doc_default``
|
``doc_default``
|
||||||
|
|
@ -643,11 +703,11 @@ the text, and add more entries to the dict mapping types to strings just above i
|
||||||
|
|
||||||
Note also that this approach takes away some possible flexibility for the format
|
Note also that this approach takes away some possible flexibility for the format
|
||||||
units starting with ``e``. It used to be possible to decide at runtime what
|
units starting with ``e``. It used to be possible to decide at runtime what
|
||||||
encoding string to pass in to ``PyArg_ParseTuple()``. But now this string must
|
encoding string to pass in to :c:func:`PyArg_ParseTuple`. But now this string must
|
||||||
be hard-coded at compile-time. This limitation is deliberate; it made supporting
|
be hard-coded at compile-time. This limitation is deliberate; it made supporting
|
||||||
this format unit much easier, and may allow for future compile-time optimizations.
|
this format unit much easier, and may allow for future compile-time optimizations.
|
||||||
This restriction does not seem unreasonable; CPython itself always passes in static
|
This restriction does not seem unreasonable; CPython itself always passes in static
|
||||||
hard-coded strings when using format units starting with ``e``.
|
hard-coded encoding strings for parameters whose format units start with ``e``.
|
||||||
|
|
||||||
|
|
||||||
Using a return converter
|
Using a return converter
|
||||||
|
|
@ -692,12 +752,17 @@ None of these take parameters. For the first three, return -1 to indicate
|
||||||
error. For ``DecodeFSDefault``, the return type is ``char *``; return a NULL
|
error. For ``DecodeFSDefault``, the return type is ``char *``; return a NULL
|
||||||
pointer to indicate an error.
|
pointer to indicate an error.
|
||||||
|
|
||||||
|
To see all the return converters Argument Clinic supports, along with
|
||||||
|
their parameters (if any),
|
||||||
|
just run ``Tools/clinic/clinic.py --converters`` for the full list.
|
||||||
|
|
||||||
|
|
||||||
Calling Python code
|
Calling Python code
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The rest of the advanced topics require you to write Python code
|
The rest of the advanced topics require you to write Python code
|
||||||
which lives inside your C file and modifies Argument Clinic at
|
which lives inside your C file and modifies Argument Clinic's
|
||||||
runtime. This is simple; you simply define a Python block.
|
runtime state. This is simple: you simply define a Python block.
|
||||||
|
|
||||||
A Python block uses different delimiter lines than an Argument
|
A Python block uses different delimiter lines than an Argument
|
||||||
Clinic function block. It looks like this::
|
Clinic function block. It looks like this::
|
||||||
|
|
@ -778,13 +843,13 @@ As we hinted at in the previous section... you can write your own converters!
|
||||||
A converter is simply a Python class that inherits from ``CConverter``.
|
A converter is simply a Python class that inherits from ``CConverter``.
|
||||||
The main purpose of a custom converter is if you have a parameter using
|
The main purpose of a custom converter is if you have a parameter using
|
||||||
the ``O&`` format unit--parsing this parameter means calling
|
the ``O&`` format unit--parsing this parameter means calling
|
||||||
a ``PyArg_ParseTuple()`` "converter function".
|
a :c:func:`PyArg_ParseTuple` "converter function".
|
||||||
|
|
||||||
Your converter class should be named ``*something*_converter``.
|
Your converter class should be named ``*something*_converter``.
|
||||||
If the name follows this convention, then your converter class
|
If the name follows this convention, then your converter class
|
||||||
will be automatically registered with Argument Clinic; its name
|
will be automatically registered with Argument Clinic; its name
|
||||||
will be the name of your class with the ``_converter`` suffix
|
will be the name of your class with the ``_converter`` suffix
|
||||||
stripped off. (This is done automatically for you with a metaclass.)
|
stripped off. (This is accomplished with a metaclass.)
|
||||||
|
|
||||||
You shouldn't subclass ``CConverter.__init__``. Instead, you should
|
You shouldn't subclass ``CConverter.__init__``. Instead, you should
|
||||||
write a ``converter_init()`` function. ``converter_init()``
|
write a ``converter_init()`` function. ``converter_init()``
|
||||||
|
|
@ -825,12 +890,13 @@ to specify in your subclass. Here's the current list:
|
||||||
``c_ignored_default``
|
``c_ignored_default``
|
||||||
The default value used to initialize the C variable when
|
The default value used to initialize the C variable when
|
||||||
there is no default, but not specifying a default may
|
there is no default, but not specifying a default may
|
||||||
result in an "uninitialized variable" warning. This is
|
result in an "uninitialized variable" warning. This can
|
||||||
easily happen when using option groups--although
|
easily happen when using option groups--although
|
||||||
properly-written code won't actually use the variable,
|
properly-written code will never actually use this value,
|
||||||
the variable does get passed in to the _impl, and the
|
the variable does get passed in to the impl, and the
|
||||||
C compiler will complain about the "use" of the uninitialized
|
C compiler will complain about the "use" of the
|
||||||
value. This value should be a string.
|
uninitialized value. This value should always be a
|
||||||
|
non-empty string.
|
||||||
|
|
||||||
``converter``
|
``converter``
|
||||||
The name of the C converter function, as a string.
|
The name of the C converter function, as a string.
|
||||||
|
|
@ -843,7 +909,7 @@ to specify in your subclass. Here's the current list:
|
||||||
``parse_by_reference``
|
``parse_by_reference``
|
||||||
A boolean value. If true,
|
A boolean value. If true,
|
||||||
Argument Clinic will add a ``&`` in front of the name of
|
Argument Clinic will add a ``&`` in front of the name of
|
||||||
the variable when passing it into ``PyArg_ParseTuple()``.
|
the variable when passing it into :c:func:`PyArg_ParseTuple`.
|
||||||
|
|
||||||
|
|
||||||
Here's the simplest example of a custom converter, from ``Modules/zlibmodule.c``::
|
Here's the simplest example of a custom converter, from ``Modules/zlibmodule.c``::
|
||||||
|
|
@ -857,9 +923,10 @@ Here's the simplest example of a custom converter, from ``Modules/zlibmodule.c``
|
||||||
[python]*/
|
[python]*/
|
||||||
/*[python checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
|
/*[python checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
|
||||||
|
|
||||||
This block adds a ``uint`` converter to Argument Clinic. Parameters
|
This block adds a converter to Argument Clinic named ``uint``. Parameters
|
||||||
declared as ``uint`` will be declared as type ``unsigned int``, and will
|
declared as ``uint`` will be declared as type ``unsigned int``, and will
|
||||||
be parsed by calling the ``uint_converter`` converter function in C.
|
be parsed by the ``'O&'`` format unit, which will call the ``uint_converter``
|
||||||
|
converter function.
|
||||||
``uint`` variables automatically support default values.
|
``uint`` variables automatically support default values.
|
||||||
|
|
||||||
More sophisticated custom converters can insert custom C code to
|
More sophisticated custom converters can insert custom C code to
|
||||||
|
|
@ -871,7 +938,7 @@ Writing a custom return converter
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Writing a custom return converter is much like writing
|
Writing a custom return converter is much like writing
|
||||||
a custom converter. Except it's much simpler, because return
|
a custom converter. Except it's somewhat simpler, because return
|
||||||
converters are themselves much simpler.
|
converters are themselves much simpler.
|
||||||
|
|
||||||
Return converters must subclass ``CReturnConverter``.
|
Return converters must subclass ``CReturnConverter``.
|
||||||
|
|
|
||||||
|
|
@ -997,7 +997,8 @@ class BlockPrinter:
|
||||||
# "languages" maps the name of the language ("C", "Python").
|
# "languages" maps the name of the language ("C", "Python").
|
||||||
# "extensions" maps the file extension ("c", "py").
|
# "extensions" maps the file extension ("c", "py").
|
||||||
languages = { 'C': CLanguage, 'Python': PythonLanguage }
|
languages = { 'C': CLanguage, 'Python': PythonLanguage }
|
||||||
extensions = { 'c': CLanguage, 'h': CLanguage, 'py': PythonLanguage }
|
extensions = { name: CLanguage for name in "c cc cpp cxx h hh hpp hxx".split() }
|
||||||
|
extensions['py'] = PythonLanguage
|
||||||
|
|
||||||
|
|
||||||
# maps strings to callables.
|
# maps strings to callables.
|
||||||
|
|
@ -2430,9 +2431,6 @@ class DSLParser:
|
||||||
|
|
||||||
# the final stanza of the DSL is the docstring.
|
# the final stanza of the DSL is the docstring.
|
||||||
def state_function_docstring(self, line):
|
def state_function_docstring(self, line):
|
||||||
if not self.function.self_converter:
|
|
||||||
self.function.self_converter = self_converter("self", self.function)
|
|
||||||
|
|
||||||
if self.group:
|
if self.group:
|
||||||
fail("Function " + self.function.name + " has a ] without a matching [.")
|
fail("Function " + self.function.name + " has a ] without a matching [.")
|
||||||
|
|
||||||
|
|
@ -2604,6 +2602,9 @@ class DSLParser:
|
||||||
if not self.function:
|
if not self.function:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self.function.self_converter:
|
||||||
|
self.function.self_converter = self_converter("self", self.function)
|
||||||
|
|
||||||
if self.keyword_only:
|
if self.keyword_only:
|
||||||
values = self.function.parameters.values()
|
values = self.function.parameters.values()
|
||||||
if not values:
|
if not values:
|
||||||
|
|
|
||||||
|
|
@ -296,9 +296,9 @@ os.stat as os_stat_fn
|
||||||
|
|
||||||
Perform a stat system call on the given path.""")
|
Perform a stat system call on the given path.""")
|
||||||
self.assertEqual("""
|
self.assertEqual("""
|
||||||
|
stat(path)
|
||||||
Perform a stat system call on the given path.
|
Perform a stat system call on the given path.
|
||||||
|
|
||||||
os.stat(path)
|
|
||||||
path
|
path
|
||||||
Path to be examined
|
Path to be examined
|
||||||
""".strip(), function.docstring)
|
""".strip(), function.docstring)
|
||||||
|
|
@ -316,9 +316,9 @@ This is the documentation for foo.
|
||||||
Okay, we're done here.
|
Okay, we're done here.
|
||||||
""")
|
""")
|
||||||
self.assertEqual("""
|
self.assertEqual("""
|
||||||
|
bar(x, y)
|
||||||
This is the documentation for foo.
|
This is the documentation for foo.
|
||||||
|
|
||||||
foo.bar(x, y)
|
|
||||||
x
|
x
|
||||||
Documentation for x.
|
Documentation for x.
|
||||||
|
|
||||||
|
|
@ -356,7 +356,7 @@ This/used to break Clinic!
|
||||||
def test_left_group(self):
|
def test_left_group(self):
|
||||||
function = self.parse_function("""
|
function = self.parse_function("""
|
||||||
module curses
|
module curses
|
||||||
curses.window.addch
|
curses.addch
|
||||||
[
|
[
|
||||||
y: int
|
y: int
|
||||||
Y-coordinate.
|
Y-coordinate.
|
||||||
|
|
@ -380,7 +380,9 @@ curses.window.addch
|
||||||
self.assertEqual(p.group, group)
|
self.assertEqual(p.group, group)
|
||||||
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
||||||
self.assertEqual(function.docstring.strip(), """
|
self.assertEqual(function.docstring.strip(), """
|
||||||
curses.window.addch([y, x,] ch, [attr])
|
addch([y, x,] ch, [attr])
|
||||||
|
|
||||||
|
|
||||||
y
|
y
|
||||||
Y-coordinate.
|
Y-coordinate.
|
||||||
x
|
x
|
||||||
|
|
@ -394,7 +396,7 @@ curses.window.addch([y, x,] ch, [attr])
|
||||||
def test_nested_groups(self):
|
def test_nested_groups(self):
|
||||||
function = self.parse_function("""
|
function = self.parse_function("""
|
||||||
module curses
|
module curses
|
||||||
curses.window.imaginary
|
curses.imaginary
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
y1: int
|
y1: int
|
||||||
|
|
@ -439,7 +441,9 @@ curses.window.imaginary
|
||||||
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
||||||
|
|
||||||
self.assertEqual(function.docstring.strip(), """
|
self.assertEqual(function.docstring.strip(), """
|
||||||
curses.window.imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, attr6]])
|
imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, attr6]])
|
||||||
|
|
||||||
|
|
||||||
y1
|
y1
|
||||||
Y-coordinate.
|
Y-coordinate.
|
||||||
y2
|
y2
|
||||||
|
|
@ -557,7 +561,7 @@ foo.bar
|
||||||
Docstring
|
Docstring
|
||||||
|
|
||||||
""")
|
""")
|
||||||
self.assertEqual("Docstring\n\nfoo.bar()", function.docstring)
|
self.assertEqual("bar()\nDocstring", function.docstring)
|
||||||
self.assertEqual(0, len(function.parameters))
|
self.assertEqual(0, len(function.parameters))
|
||||||
|
|
||||||
def test_illegal_module_line(self):
|
def test_illegal_module_line(self):
|
||||||
|
|
@ -652,9 +656,9 @@ foo.bar
|
||||||
Not at column 0!
|
Not at column 0!
|
||||||
""")
|
""")
|
||||||
self.assertEqual("""
|
self.assertEqual("""
|
||||||
|
bar(x, *, y)
|
||||||
Not at column 0!
|
Not at column 0!
|
||||||
|
|
||||||
foo.bar(x, *, y)
|
|
||||||
x
|
x
|
||||||
Nested docstring here, goeth.
|
Nested docstring here, goeth.
|
||||||
""".strip(), function.docstring)
|
""".strip(), function.docstring)
|
||||||
|
|
@ -666,7 +670,7 @@ os.stat
|
||||||
path: str
|
path: str
|
||||||
This/used to break Clinic!
|
This/used to break Clinic!
|
||||||
""")
|
""")
|
||||||
self.assertEqual("This/used to break Clinic!\n\nos.stat(path)", function.docstring)
|
self.assertEqual("stat(path)\nThis/used to break Clinic!", function.docstring)
|
||||||
|
|
||||||
def test_directive(self):
|
def test_directive(self):
|
||||||
c = FakeClinic()
|
c = FakeClinic()
|
||||||
|
|
@ -692,7 +696,7 @@ This/used to break Clinic!
|
||||||
def parse_function(self, text):
|
def parse_function(self, text):
|
||||||
block = self.parse(text)
|
block = self.parse(text)
|
||||||
s = block.signatures
|
s = block.signatures
|
||||||
assert len(s) == 2
|
self.assertEqual(len(s), 2)
|
||||||
assert isinstance(s[0], clinic.Module)
|
assert isinstance(s[0], clinic.Module)
|
||||||
assert isinstance(s[1], clinic.Function)
|
assert isinstance(s[1], clinic.Function)
|
||||||
return s[1]
|
return s[1]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue