PEP 570: Edit Sections and How to Teach (GH-975)
* Correct grammar and style. * Update the "How to teach" section. * Expand grammar specification. Signed-off-by: Carol Willing <carolcode@willingconsulting.com>
This commit is contained in:
parent
4b703079ff
commit
1e2f409b47
346
pep-0570.rst
346
pep-0570.rst
|
@ -19,27 +19,34 @@ Abstract
|
|||
========
|
||||
|
||||
This PEP proposes to introduce a new syntax, ``/``, for specifying
|
||||
positional-only parameters in Python.
|
||||
positional-only parameters in Python function definitions.
|
||||
|
||||
Positional-only parameters have no externally-usable name. When a function
|
||||
accepting positional-only parameters is called, positional arguments are mapped
|
||||
to these parameters based solely on their position.
|
||||
to these parameters based solely on their order.
|
||||
|
||||
Design of APIs (application programmable interfaces) is important for library
|
||||
authors to ensure correct and intended usage of an API. The inability to
|
||||
specify which parameters are positional-only requires careful consideration
|
||||
when choosing appropriate parameter names, even if the parameters are required
|
||||
or have no external semantic meaning for callers of the API.
|
||||
When designing APIs (application programmable interfaces), library
|
||||
authors try to ensure correct and intended usage of an API. Without the ability to
|
||||
specify which parameters are positional-only, library authors must use careful consideration
|
||||
when choosing appropriate parameter names. This consideration must be taken
|
||||
into account even if the function requires the parameters or the parameters
|
||||
have no external semantic meaning for callers of the API.
|
||||
|
||||
In this PEP, we discuss Python's history and current semantics for
|
||||
positional-only parameters, the problems encountered by not having them, how
|
||||
these problems are handled without language-intrinsic support, and the benefits
|
||||
of having positional-only parameters. With context of the motivation, we then
|
||||
discuss the rationale for why this change should be a feature intrinsic to the
|
||||
language. Next, we propose the syntax for demarcating positional-only
|
||||
parameters. Following, we present how to teach this new feature. Finally, we
|
||||
conclude with noting rejected ideas in further detail, complementing the
|
||||
rationale.
|
||||
In this PEP, we discuss:
|
||||
|
||||
* Python's history and current semantics for positional-only parameters
|
||||
* the problems encountered by not having them
|
||||
* how these problems are handled without language-intrinsic support for
|
||||
positional-only parameters
|
||||
* the benefits of having positional-only parameters
|
||||
|
||||
Within context of the motivation, we then:
|
||||
|
||||
* discuss why positional-only parameters should be a feature intrinsic to the
|
||||
language
|
||||
* propose the syntax for demarcating positional-only parameters
|
||||
* present how to teach this new feature
|
||||
* note rejected ideas in further detail
|
||||
|
||||
==========
|
||||
Motivation
|
||||
|
@ -50,30 +57,31 @@ History of Positional-Only Parameter Semantics in Python
|
|||
--------------------------------------------------------
|
||||
|
||||
Python originally supported positional-only parameters. Early versions of the
|
||||
language lacked the ability to call functions binding arguments to a parameter
|
||||
language lacked the ability to call functions with arguments bound to parameters
|
||||
by name. Around Python 1.0, parameter semantics changed to be
|
||||
positional-or-keyword. Since then, callers have been able to provide arguments
|
||||
to a function either positionally or by the keyword name specified in the
|
||||
function's definition.
|
||||
|
||||
In current versions of Python, many CPython "builtin" and standard library
|
||||
functions only accept positional-only arguments. The resulting semantics can be
|
||||
functions only accept positional-only parameters. The resulting semantics can be
|
||||
easily observed by calling one of these functions using keyword arguments::
|
||||
|
||||
>>> help(pow)
|
||||
...
|
||||
pow(x, y, z=None, /)
|
||||
...
|
||||
|
||||
>>> pow(x=5, y=3)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: pow() takes no keyword arguments
|
||||
|
||||
``pow()`` clearly expresses that its arguments are positional-only via the
|
||||
``pow()`` expresses that its parameters are positional-only via the
|
||||
``/`` marker. However, this is only a documentation convention; Python
|
||||
developers cannot use this syntax in code.
|
||||
|
||||
There are other functions with other interesting semantics:
|
||||
There are functions with other interesting semantics:
|
||||
|
||||
* ``range()``, an overloaded function, accepts an optional parameter to the
|
||||
*left* of its required parameter. [#RANGE]_
|
||||
|
@ -83,10 +91,10 @@ There are other functions with other interesting semantics:
|
|||
would occlude that name going into the ``**kwarg`` keyword variadic parameter
|
||||
dict. [#DICT]_
|
||||
|
||||
One can emulate the aforementioned semantics in Python code by accepting
|
||||
One can emulate these semantics in Python code by accepting
|
||||
``(*args, **kwargs)`` and parsing the arguments manually. However, this results
|
||||
in a disconnect between the function definition and what the function
|
||||
contractually accepts — the function definition does not match the logic of the
|
||||
contractually accepts. The function definition does not match the logic of the
|
||||
argument handling.
|
||||
|
||||
Additionally, the ``/`` syntax is used beyond CPython for specifying similar
|
||||
|
@ -107,39 +115,39 @@ Challenges for Library Authors
|
|||
|
||||
With positional-or-keyword parameters, the mix of calling conventions is not
|
||||
always desirable. Authors may want to restrict usage of an API by disallowing
|
||||
calling them with keyword arguments, which exposes the name of the parameter as
|
||||
part of the public API. This is especially useful for required parameters that
|
||||
already have semantic meaning with respect to function (e.g,
|
||||
calling the API with keyword arguments, which exposes the name of the parameter when
|
||||
part of the public API. This approach is especially useful for required function
|
||||
parameters that already have semantic meaning (e.g,
|
||||
``namedtuple(typenames, field_names, …)`` or when the parameter name has no
|
||||
true external meaning (e.g., ``arg1``, ``arg2``, …, etc for ``min()``). If a
|
||||
caller of an API starts using a keyword argument, the parameter cannot be
|
||||
renamed because it would be a breaking change.
|
||||
caller of an API starts using a keyword argument, the library author cannot rename
|
||||
the parameter because it would be a breaking change.
|
||||
|
||||
Positional-only parameters can be emulated by extracting arguments from
|
||||
``*args`` one by one. However, this approach is error-prone and is not
|
||||
synonymous with the function definition, as previously mentioned. The usage of
|
||||
the function is ambiguous and forces callers to look at ``help()`` or the
|
||||
associated auto-generated documentation to understand what parameters the
|
||||
function contractually accepts.
|
||||
the function is ambiguous and forces callers to look at ``help()``, the
|
||||
associated auto-generated documentation, or source code to understand what
|
||||
parameters the function contractually accepts.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Challenges for Callers of an API
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Callers may be surprised when first encountering this notation. This is
|
||||
expected given that it has only recently been documented
|
||||
Callers may be surprised when first encountering positional-only notation. This
|
||||
is expected given that it has only recently been documented
|
||||
[#document-positional-only]_ and it is not possible to use in Python code. For
|
||||
these reasons, this notation is currently an outlier that appears only in
|
||||
CPython's APIs developed in C. Documenting the notation and making it possible
|
||||
to be used in Python code would eliminate this disconnect.
|
||||
|
||||
Furthermore, the documentation for positional-only parameters is inconsistent:
|
||||
Furthermore, the current documentation for positional-only parameters is inconsistent:
|
||||
|
||||
* Some functions denote optional groups of positional-only arguments by
|
||||
* Some functions denote optional groups of positional-only parameters by
|
||||
enclosing them in nested square brackets. [#BORDER]_
|
||||
|
||||
* Some functions denote optional groups of positional-only arguments by
|
||||
presenting multiple prototypes with varying numbers of arguments.
|
||||
* Some functions denote optional groups of positional-only parameters by
|
||||
presenting multiple prototypes with varying numbers of parameters.
|
||||
[#SENDFILE]_
|
||||
|
||||
* Some functions use *both* of the above approaches. [#RANGE]_ [#ADDCH]_
|
||||
|
@ -147,35 +155,41 @@ Furthermore, the documentation for positional-only parameters is inconsistent:
|
|||
Another point of consideration the current documentation does not distinguish
|
||||
whether a function takes positional-only parameters. ``open()`` accepts keyword
|
||||
arguments; however, ``ord()`` does not — there is no way of telling just by
|
||||
reading the documentation.
|
||||
reading the existing documentation.
|
||||
|
||||
--------------------------------------
|
||||
Benefits of Positional-Only Parameters
|
||||
--------------------------------------
|
||||
|
||||
Positional-only parameters gives more control to library authors to better
|
||||
Positional-only parameters give more control to library authors to better
|
||||
express the intended usage of an API and allows the API to evolve in a safe,
|
||||
backward-compatible way. Additionally, it makes the Python language more
|
||||
consistent with respect to existing documentation and the behavior of various
|
||||
consistent with existing documentation and the behavior of various
|
||||
"builtin" and standard library functions.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Empowering Library Authors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Library authors would have have the flexibility to change the name of
|
||||
positional-only parameters without breaking callers. It reduces the
|
||||
Library authors would have the flexibility to change the name of
|
||||
positional-only parameters without breaking callers. This flexibility reduces the
|
||||
cognitive burden for choosing an appropriate public-facing name for required
|
||||
parameters or parameters that have no true external semantic meaning.
|
||||
|
||||
Positional-only parameters are useful in several situations. An extreme
|
||||
Positional-only parameters are useful in several situations such as:
|
||||
|
||||
* when a function accepts any keyword argument but also can accept a positional one
|
||||
* when a parameter has no external semantic meaning
|
||||
* when an API's parameters are required and unambiguous
|
||||
|
||||
A key
|
||||
scenario is when a function accepts any keyword argument but can also accepts a
|
||||
positional one. Prominent examples are ``Formatter.format`` and
|
||||
``dict.update``. For instance, ``dict.update`` accepts a dictionary
|
||||
(positionally), an iterable of key/value pairs (positionally), or multiple
|
||||
keyword arguments. In this scenario, if the dictionary parameter were not
|
||||
positional-only, the user could not use the name that the function definition
|
||||
uses for said parameter or, conversely, the function could not distinguish
|
||||
uses for the parameter or, conversely, the function could not distinguish
|
||||
easily if the argument received is the dictionary/iterable or a keyword
|
||||
argument for updating the key/value pair.
|
||||
|
||||
|
@ -190,13 +204,13 @@ The name of the parameter provides no intrinsic value and forces the API author
|
|||
to maintain its name forever since callers might pass ``x`` as a keyword
|
||||
argument.
|
||||
|
||||
Additionally, positional-only arguments are useful when an API's parameters
|
||||
Additionally, positional-only parameters are useful when an API's parameters
|
||||
are required and is unambiguous with respect to function. For example::
|
||||
|
||||
def add_to_queue(item: QueueItem):
|
||||
...
|
||||
|
||||
It is clear by the name of the function the argument expected. A keyword
|
||||
The name of the function makes clear the argument expected. A keyword
|
||||
argument provides minimal benefit and also limits the future evolution of the
|
||||
API. Say at a later time we want this function to be able to take multiple
|
||||
items, while preserving backwards compatibility::
|
||||
|
@ -210,9 +224,9 @@ or to take them by using argument lists::
|
|||
...
|
||||
|
||||
the author would be forced to always keep the original parameter name to avoid
|
||||
potentially break callers.
|
||||
potentially breaking callers.
|
||||
|
||||
By being able to specify positional-only arguments, an author can change the
|
||||
By being able to specify positional-only parameters, an author can change the
|
||||
name of the parameters freely or even change them to ``*args``, as seen in the
|
||||
previous example. There are multiple function definitions in the standard
|
||||
library which fall into this category. For example, the required parameter to
|
||||
|
@ -224,7 +238,7 @@ keyword to the name ``self`` when calling the method from the class::
|
|||
io.FileIO.write(self=f, b=b"data")
|
||||
|
||||
Indeed, function definitions from the standard library implemented in C usually
|
||||
take ``self`` as a positional-only argument::
|
||||
take ``self`` as a positional-only parameter::
|
||||
|
||||
>>> help(io.FileIO.write)
|
||||
Help on method_descriptor:
|
||||
|
@ -239,14 +253,15 @@ Improving Language Consistency
|
|||
The Python language, itself, would be more consistent with positional-only
|
||||
parameters. If the concept is a normal feature of Python rather than a feature
|
||||
exclusive to extension modules, it would reduce confusion for users
|
||||
encountering functions with positional-only arguments. Again, major
|
||||
encountering functions with positional-only parameters. Some major
|
||||
third-party packages are already using the ``/`` notation in their function
|
||||
definitions [#numpy-ufuncs]_ [#scipy-gammaln]_.
|
||||
|
||||
Additionally, this would bridge the gap found between "builtin" functions which
|
||||
Bridging the gap found between "builtin" functions which
|
||||
specify positional-only parameters and pure Python implementations that lack
|
||||
the syntax for it. The ``/`` syntax is already exposed in the documentation of
|
||||
some builtins and interfaces generated by the argument clinic.
|
||||
the positional syntax would improve consistency. The ``/`` syntax is already exposed
|
||||
in the existing documentation such as when builtins and interfaces are generated
|
||||
by the argument clinic.
|
||||
|
||||
Another essential aspect to consider is PEP 399 [#PEP399]_, which mandates that
|
||||
pure Python versions of modules in the standard library *must* have the same
|
||||
|
@ -263,14 +278,18 @@ We propose to introduce positional-only parameters as a new syntax to the
|
|||
Python language.
|
||||
|
||||
The new syntax would enable library authors to further control how their API
|
||||
can be called. It would restrict arguments to be called as positional-only,
|
||||
while not allowing them to be called as keyword arguments.
|
||||
can be called. It would designate which parameters must be called as
|
||||
positional-only, while preventing them from being called as keyword arguments.
|
||||
|
||||
Previously, PEP 457 proposed to define the syntax, but with a much broader
|
||||
scope. This PEP takes the original proposal a step further that by justifying
|
||||
scope. This PEP takes the original proposal a step further by justifying
|
||||
the syntax and providing an implementation for the ``/`` syntax in function
|
||||
definitions.
|
||||
|
||||
-----------
|
||||
Performance
|
||||
-----------
|
||||
|
||||
In addition to the aforementioned benefits, the parsing and handling of
|
||||
positional-only arguments is faster. This performance benefit can be
|
||||
demonstrated in this thread about converting keyword arguments to positional:
|
||||
|
@ -279,6 +298,10 @@ trend towards moving builtins away from keyword arguments: recently,
|
|||
backwards-incompatible changes were made to disallow keyword arguments to
|
||||
``bool``, ``float``, ``list``, ``int``, ``tuple``.
|
||||
|
||||
---------------
|
||||
Maintainability
|
||||
---------------
|
||||
|
||||
Providing a way to specify positional-only parameters in Python would make it
|
||||
easier to maintain pure Python implementations of C modules. Additionally,
|
||||
library authors defining functions would have the choice for choosing
|
||||
|
@ -302,6 +325,10 @@ This is a well discussed, recurring topic on the Python mailing lists:
|
|||
* May 2006: `Benji York: [Python-Dev] Positional-only Arguments
|
||||
<https://mail.python.org/pipermail/python-dev/2006-May/064790.html>`_
|
||||
|
||||
----------------
|
||||
Logical ordering
|
||||
----------------
|
||||
|
||||
Positional-only parameters also have the (minor) benefit of enforcing some
|
||||
logical order when calling interfaces that make use of them. For example, the
|
||||
``range`` function takes all its parameters positionally and disallows forms
|
||||
|
@ -317,18 +344,22 @@ intended order::
|
|||
|
||||
range(start=0, stop=5, step=2)
|
||||
|
||||
Another critical aspect which motivates positional-only parameters is PEP 399
|
||||
-------------------------------------------
|
||||
Compatibility for Pure Python and C modules
|
||||
-------------------------------------------
|
||||
|
||||
Another critical motivation for positional-only parameters is PEP 399
|
||||
[#PEP399]_: Pure Python/C Accelerator Module Compatibility Requirements. This
|
||||
PEP states that :
|
||||
PEP states that:
|
||||
|
||||
This PEP requires that in these instances that the C code must pass the
|
||||
test suite used for the pure Python code to act as much as a drop-in
|
||||
replacement as reasonably possible
|
||||
|
||||
It is clear that if the C code is implemented using the existing capabilities
|
||||
If the C code is implemented using the existing capabilities
|
||||
to implement positional-only parameters using the argument clinic, and related
|
||||
machinery, it is not possible for the pure Python counterpart to match the
|
||||
provided interface and requirements. This also creates a disparity between the
|
||||
provided interface and requirements. This creates a disparity between the
|
||||
interfaces of some functions and classes in the CPython standard library and
|
||||
other Python implementations. For example::
|
||||
|
||||
|
@ -345,8 +376,12 @@ goes against the spirit of PEP 399 [#PEP399]_ to avoid duplication of effort by
|
|||
mandating that all modules added to Python's standard library **must** have a
|
||||
pure Python implementation with the same interface and semantics.
|
||||
|
||||
-------------------------
|
||||
Consistency in Subclasses
|
||||
-------------------------
|
||||
|
||||
Another scenario where positional-only parameters provide benefit occurs when a
|
||||
subclass overrides a method of the base class and changes the name of arguments
|
||||
subclass overrides a method of the base class and changes the name of parameters
|
||||
that are intended to be positional::
|
||||
|
||||
class Base:
|
||||
|
@ -376,9 +411,13 @@ hasn't been written yet and over which the author has no control. Having
|
|||
measures that can facilitate the evolution of interfaces in a
|
||||
backwards-compatible would be useful for library authors.
|
||||
|
||||
-------------
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
A final argument in favor of positional-only parameters is that they allow some
|
||||
new optimizations like the ones already present in the argument clinic due to
|
||||
the fact that parameters must be passed in strict order. For example, CPython's
|
||||
the fact that parameters are expected to be passed in strict order. For example, CPython's
|
||||
internal *METH_FASTCALL* calling convention has been recently specialized for
|
||||
functions with positional-only parameters to eliminate the cost for handling
|
||||
empty keywords. Similar performance improvements can be applied when creating
|
||||
|
@ -403,15 +442,20 @@ like::
|
|||
def name(positional_only_parameters, /, positional_or_keyword_parameters,
|
||||
*, keyword_only_parameters):
|
||||
|
||||
All parameters left of the ``/`` are demarcated as positional-only. If ``/``
|
||||
is not specified in the function definition, that function does not accept any
|
||||
positional-only arguments. The logic around optional values for
|
||||
positional-only arguments remains the same as for positional-or-keyword
|
||||
arguments. Once a positional-only parameter is specified with a default, the
|
||||
following positional-only and positional-or-keyword arguments need to have
|
||||
defaults as well. Positional-only parameters which do not have a default
|
||||
values are *required* positional-only parameters. Therefore the following are
|
||||
valid signatures::
|
||||
The following would apply:
|
||||
|
||||
* All parameters left of the ``/`` are demarcated as positional-only.
|
||||
* If ``/`` is not specified in the function definition, that function does not
|
||||
accept any positional-only arguments.
|
||||
* The logic around optional values for positional-only parameters remains the
|
||||
same as for positional-or-keyword parameters.
|
||||
* Once a positional-only parameter is specified with a default, the
|
||||
following positional-only and positional-or-keyword parameters need to have
|
||||
defaults as well.
|
||||
* Positional-only parameters which do not have default
|
||||
values are *required* positional-only parameters.
|
||||
|
||||
Therefore the following would be valid function definitions::
|
||||
|
||||
def name(p1, p2, /, p_or_kw, *, kw):
|
||||
def name(p1, p2=None, /, p_or_kw=None, *, kw):
|
||||
|
@ -420,7 +464,12 @@ valid signatures::
|
|||
def name(p1, p2, /, p_or_kw):
|
||||
def name(p1, p2, /):
|
||||
|
||||
While the followings are not::
|
||||
Just like today, the following would be valid function definitions::
|
||||
|
||||
def name(p_or_kw, *, kw):
|
||||
def name(*, kw):
|
||||
|
||||
While the following would be invalid::
|
||||
|
||||
def name(p1, p2=None, /, p_or_kw, *, kw):
|
||||
def name(p1=None, p2, /, p_or_kw=None, *, kw):
|
||||
|
@ -430,21 +479,40 @@ While the followings are not::
|
|||
Full grammar specification
|
||||
--------------------------
|
||||
|
||||
A draft of the proposed grammar specification is::
|
||||
A simplified view of the proposed grammar specification is::
|
||||
|
||||
new_typedargslist:
|
||||
tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [typedargslist]] | typedargslist
|
||||
typedargslist:
|
||||
tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' # and so on
|
||||
|
||||
new_varargslist:
|
||||
vfpdef ['=' test] (',' vfpdef ['=' test])* ',' '/' [',' [varargslist]] | varargslist
|
||||
varargslist:
|
||||
vfpdef ['=' test] (',' vfpdef ['=' test])* ',' '/' [',' # and so on
|
||||
|
||||
It would be added to the actual ``typedargslist`` and ``varargslist``, but for
|
||||
more relaxed discussion it is presented as ``new_typedargslist`` and
|
||||
``new_varargslist``. Note that using a construction with two new rules
|
||||
(``new_varargslist`` and ``new_varargslist``) is not possible with the current
|
||||
parser as a rule is not LL(1). This is the reason the rule needs to be
|
||||
included in the existing ``typedargslist`` and ``varargslist`` (in the same way
|
||||
keyword-only arguments were introduced).
|
||||
Based on the reference implementation in this PEP, the new rule for
|
||||
``typedarglist`` would be::
|
||||
|
||||
typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
|
||||
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [',']]]
|
||||
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [',']] ] )| (
|
||||
tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
|
||||
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [',']]]
|
||||
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [','])
|
||||
|
||||
and for ``varargslist`` would be::
|
||||
|
||||
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
|
||||
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']]]
|
||||
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
|
||||
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']]]
|
||||
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']
|
||||
)
|
||||
|
||||
--------------------------------
|
||||
Origin of the "/" as a separator
|
||||
|
@ -460,32 +528,30 @@ in 2012 [#GUIDO]_ :
|
|||
How to teach this
|
||||
=================
|
||||
|
||||
Since this concept is closely analogous to keyword-only arguments, introducing
|
||||
a dedicated syntax to mark positional-only arguments may in fact make it
|
||||
*easier* to teach the possible function definitions a user may encounter or
|
||||
design, by teaching the two concepts together.
|
||||
Introducing a dedicated syntax to mark positional-only parameters is closely
|
||||
analogous to existing keyword-only arguments. Teaching these concepts together
|
||||
may *simplify* how to teach the possible function definitions a user may encounter or
|
||||
design.
|
||||
|
||||
This PEP recommends adding a new subsection to the Python documentation, in the
|
||||
section `"More on Defining Functions"`_, where the rest of the argument types
|
||||
are discussed. The following paragraphs serve as a draft for these additions
|
||||
that will serve to introduce the notation for both positional-only and
|
||||
keyword-only parameters. It does not intend to be exhaustive, nor should it be
|
||||
are discussed. The following paragraphs serve as a draft for these additions.
|
||||
They will introduce the notation for both positional-only and
|
||||
keyword-only parameters. It is not intended to be exhaustive, nor should it be
|
||||
considered the final version to be incorporated into the documentation.
|
||||
|
||||
|
||||
.. _"More on Defining Functions": https://docs.python.org/3.7/tutorial/controlflow.html#more-on-defining-functions
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
By default, all arguments passed to a Python function can be either by position
|
||||
or explicitly by keyword. Occasionally, it makes sense to restrict the way
|
||||
arguments can be passed. To this end, it is possible to mark certain parameters
|
||||
as *positional-only*, meaning that they cannot be passed by keyword. This can
|
||||
be achieved by placing a ``/`` (forward-slash) in the arguments list after the
|
||||
last positional-only parameter. In order to mark parameters as *keyword-only*,
|
||||
meaning that they can *only* be passed by keyword argument, place a ``*`` in
|
||||
the arguments list before the first keyword-only parameter.
|
||||
By default, arguments may be passed to a Python function either by position
|
||||
or explicitly by keyword. For readability and performance, it makes sense to
|
||||
restrict the way arguments can be passed so that a developer need only look
|
||||
at the function definition to determine if items are passed by position, by
|
||||
position or keyword, or by keyword.
|
||||
|
||||
A function definition which makes use of both of these features may look like::
|
||||
A function definition may look like::
|
||||
|
||||
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
|
||||
----------- ---------- ----------
|
||||
|
@ -494,7 +560,46 @@ A function definition which makes use of both of these features may look like::
|
|||
| - Keyword only
|
||||
-- Positional only
|
||||
|
||||
Consider the following example functions::
|
||||
where ``/`` and ``*`` are optional. If used, these symbols indicate the kind of
|
||||
parameter by how the arguments may be passed to the function:
|
||||
positional-only, positional-or-keyword, and keyword-only. Keyword parameters
|
||||
are also referred to as named parameters.
|
||||
|
||||
-------------------------------
|
||||
Positional-or-Keyword Arguments
|
||||
-------------------------------
|
||||
|
||||
If ``/`` and ``*`` are not present in the function definition, arguments may
|
||||
be passed to a function by position or by keyword.
|
||||
|
||||
--------------------------
|
||||
Positional-only Parameters
|
||||
--------------------------
|
||||
|
||||
Looking at this in a bit more detail, it is possible to mark certain parameters
|
||||
as *positional-only*. If *positional-only*, the parameters' order matters, and
|
||||
the parameters cannot be passed by keyword. Positional-only parameters would
|
||||
be placed before a ``/`` (forward-slash). The ``/`` is used to logically
|
||||
separate the positional-only parameters from the rest of the parameters.
|
||||
If there is no ``/`` in the function definition, there are no positional-only
|
||||
parameters.
|
||||
|
||||
Parameters following the ``/`` may be *positional-or-keyword* or *keyword-only*.
|
||||
|
||||
----------------------
|
||||
Keyword-only Arguments
|
||||
----------------------
|
||||
|
||||
To mark parameters as *keyword-only*, indicating the parameters must be passed
|
||||
by keyword argument, place an ``*`` in the arguments list just before the first
|
||||
*keyword-only* parameter.
|
||||
|
||||
-----------------
|
||||
Function Examples
|
||||
-----------------
|
||||
|
||||
Consider the following example function definitions paying close attention to the
|
||||
markers ``/`` and ``*``::
|
||||
|
||||
>>> def standard_arg(arg):
|
||||
... print(arg)
|
||||
|
@ -509,28 +614,35 @@ Consider the following example functions::
|
|||
... print(pos_only, standard, kwd_only)
|
||||
|
||||
|
||||
The first places no restrictions on the calling convention::
|
||||
The first function definition ``standard_arg``, the most familiar form,
|
||||
places no restrictions on the calling convention and arguments may be
|
||||
passed by position or keyword::
|
||||
|
||||
>>> standard_arg(2)
|
||||
2
|
||||
|
||||
>>> standard_arg(arg=2)
|
||||
2
|
||||
|
||||
The second is restricted to only use positional arguments::
|
||||
The second function ``pos_only_arg` is restricted to only use positional
|
||||
parameters as there is a ``/`` in the function definition::
|
||||
|
||||
>>> pos_only_arg(1)
|
||||
1
|
||||
|
||||
>>> pos_only_arg(arg=1)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
|
||||
|
||||
The third only allows keyword arguments::
|
||||
The third function ``kwd_only_args`` only allows keyword arguments as indicated
|
||||
by a ``*`` in the function definition::
|
||||
|
||||
>>> kwd_only_arg(3)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
|
||||
|
||||
>>> kwd_only_arg(arg=3)
|
||||
3
|
||||
|
||||
|
@ -541,15 +653,32 @@ definition::
|
|||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: combined_example() takes 2 positional arguments but 3 were given
|
||||
|
||||
>>> combined_example(1, 2, kwd_only=3)
|
||||
1 2 3
|
||||
|
||||
>>> combined_example(1, standard=2, kwd_only=3)
|
||||
1 2 3
|
||||
|
||||
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
|
||||
|
||||
-----
|
||||
Recap
|
||||
-----
|
||||
|
||||
The use case will determine which parameters to use in the function definition::
|
||||
|
||||
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
|
||||
|
||||
As guidance:
|
||||
|
||||
* Use positional-only if names do not matter or have no meaning, and there are
|
||||
only a few arguments which will always be passed in the same order.
|
||||
* Use keyword-only when names have meaning and the function definition is
|
||||
more understandable by being explicit with names.
|
||||
|
||||
========================
|
||||
Reference Implementation
|
||||
|
@ -572,7 +701,7 @@ Do Nothing
|
|||
----------
|
||||
|
||||
Always an option — the status quo. While this was considered, the
|
||||
aforementioned benefits is worth the additional complexity to the language.
|
||||
aforementioned benefits are worth the addition to the language.
|
||||
|
||||
----------
|
||||
Decorators
|
||||
|
@ -586,10 +715,10 @@ additional syntax. However, we have decided to reject this idea because:
|
|||
|
||||
* It introduces an asymmetry with how parameter behavior is declared.
|
||||
|
||||
* It makes it difficult to safely for static analyzers and type checkers to
|
||||
* It makes it difficult for static analyzers and type checkers to
|
||||
safely identify positional-only parameters. They would need to query the AST
|
||||
for the list of decorators and identify the correct one by name or with extra
|
||||
heuristics, whereas opposed to how keyword-only parameters are exposed
|
||||
heuristics, while keyword-only parameters are exposed
|
||||
directly in the AST. In order for tools to correctly identify
|
||||
positional-only parameters, they would need to execute the module to access
|
||||
any metadata the decorator is setting.
|
||||
|
@ -640,8 +769,8 @@ a new syntax because:
|
|||
* It is not symmetric with how the keyword-only parameters are currently
|
||||
declared.
|
||||
|
||||
* Querying the AST for positional-only parameters would require checking the
|
||||
normal arguments and inspecting their names, whereas keyword-only parameters
|
||||
* Querying the AST for positional-only parameters would require checking the
|
||||
normal arguments and inspecting their names, whereas keyword-only parameters
|
||||
have a property associated with them (``FunctionDef.args.kwonlyargs``).
|
||||
|
||||
* Every parameter would need to be inspected to know when positional-only
|
||||
|
@ -682,6 +811,7 @@ reasons:
|
|||
------------------------
|
||||
After separator proposal
|
||||
------------------------
|
||||
|
||||
Demarcating positional-parameters after the ``/`` was another consideration.
|
||||
However, we were unable to find an approach which would modify the arguments
|
||||
after the marker. Otherwise, would force the parameters before the marker to
|
||||
|
|
Loading…
Reference in New Issue