<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<br>
I've contributed a new PEP to humanity. I include the RST for your
reading pleasure below, but you can also read it online here:<br>
<blockquote>
<meta http-equiv="content-type" content="text/html;
charset=ISO-8859-1">
<a href="http://www.python.org/dev/peps/pep-0457/">http://www.python.org/dev/peps/pep-0457/</a></blockquote>
<br>
Discuss,<br>
<br>
<br>
<i>/arry</i><br>
<br>
-----<br>
<br>
PEP: 457<br>
Title: Syntax For Positional-Only Parameters<br>
Version: $Revision$<br>
Last-Modified: $Date$<br>
Author: Larry Hastings <a class="moz-txt-link-rfc2396E" href="mailto:larry@hastings.org"><larry@hastings.org></a><br>
Discussions-To: Python-Dev <a class="moz-txt-link-rfc2396E" href="mailto:python-dev@python.org"><python-dev@python.org></a><br>
Status: Draft<br>
Type: Informational<br>
Content-Type: text/x-rst<br>
Created: 08-Oct-2013<br>
<br>
<br>
========<br>
Overview<br>
========<br>
<br>
This PEP proposes a syntax for positional-only parameters in Python.<br>
Positional-only parameters are parameters without an
externally-usable<br>
name; when a function accepting positional-only parameters is
called,<br>
positional arguments are mapped to these parameters based solely on<br>
their position.<br>
<br>
=========<br>
Rationale<br>
=========<br>
<br>
Python has always supported positional-only parameters.<br>
Early versions of Python lacked the concept of specifying<br>
parameters by name, so naturally all parameters were<br>
positional-only. This changed around Python 1.0, when<br>
all parameters suddenly became positional-or-keyword.<br>
But, even in current versions of Python, many CPython<br>
"builtin" functions still only accept positional-only<br>
arguments.<br>
<br>
Functions implemented in modern Python can accept<br>
an arbitrary number of positional-only arguments, via the<br>
variadic ``*args`` parameter. However, there is no Python<br>
syntax to specify accepting a specific number of<br>
positional-only parameters. Put another way, there are<br>
many builtin functions whose signatures are simply not<br>
expressable with Python syntax.<br>
<br>
This PEP proposes a backwards-compatible syntax that should<br>
permit implementing any builtin in pure Python code.<br>
<br>
-----------------------------------------------------<br>
Positional-Only Parameter Semantics In Current Python<br>
-----------------------------------------------------<br>
<br>
There are many, many examples of builtins that only<br>
accept positional-only parameters. The resulting<br>
semantics are easily experienced by the Python<br>
programmer--just try calling one, specifying its<br>
arguments by name::<br>
<br>
>>> pow(x=5, y=3)<br>
Traceback (most recent call last):<br>
File "<stdin>", line 1, in <module><br>
TypeError: pow() takes no keyword arguments<br>
<br>
In addition, there are some functions with particularly<br>
interesting semantics:<br>
<br>
* ``range()``, which accepts an optional parameter<br>
to the *left* of its required parameter. [#RANGE]_<br>
<br>
* ``dict()``, whose mapping/iterator parameter is optional and<br>
semantically must be positional-only. Any externally<br>
visible name for this parameter would occlude<br>
that name going into the ``**kwarg`` keyword variadic<br>
parameter dict! [#DICT]_<br>
<br>
Obviously one can simulate any of these in pure Python code<br>
by accepting ``(*args, **kwargs)`` and parsing the arguments<br>
by hand. But this results in a disconnect between the<br>
Python function's signature and what it actually accepts,<br>
not to mention the work of implementing said argument parsing.<br>
<br>
==========<br>
Motivation<br>
==========<br>
<br>
This PEP does not propose we implement positional-only<br>
parameters in Python. The goal of this PEP is simply<br>
to define the syntax, so that:<br>
<br>
* Documentation can clearly, unambiguously, and<br>
consistently express exactly how the arguments<br>
for a function will be interpreted.<br>
<br>
* The syntax is reserved for future use, in case<br>
the community decides someday to add positional-only<br>
parameters to the language.<br>
<br>
* Argument Clinic can use a variant of the syntax<br>
as part of its input when defining<br>
the arguments for built-in functions.<br>
<br>
=================================================================<br>
The Current State Of Documentation For Positional-Only Parameters<br>
=================================================================<br>
<br>
The documentation for positional-only parameters is incomplete<br>
and inconsistent:<br>
<br>
* Some functions denote optional groups of positional-only arguments<br>
by enclosing them in nested square brackets. [#BORDER]_<br>
<br>
* Some functions denote optional groups of positional-only arguments<br>
by presenting multiple prototypes with varying numbers of<br>
arguments. [#SENDFILE]_<br>
<br>
* Some functions use *both* of the above approaches. [#RANGE]_
[#ADDCH]_<br>
<br>
One more important idea to consider: currently in the documentation<br>
there's no way to tell whether a function takes positional-only<br>
parameters. ``open()`` accepts keyword arguments, ``ord()`` does<br>
not, but there is no way of telling just by reading the<br>
documentation that this is true.<br>
<br>
====================<br>
Syntax And Semantics<br>
====================<br>
<br>
From the "ten-thousand foot view", and ignoring ``*args`` and
``**kwargs``<br>
for now, the grammar for a function definition currently looks like
this::<br>
<br>
def name(positional_or_keyword_parameters, *,
keyword_only_parameters):<br>
<br>
Building on that perspective, the new syntax for functions would
look<br>
like this::<br>
<br>
def name(positional_only_parameters, /,
positional_or_keyword_parameters,<br>
*, keyword_only_parameters):<br>
<br>
All parameters before the ``/`` are positional-only. If ``/`` is<br>
not specified in a function signature, that function does not<br>
accept any positional-only parameters.<br>
<br>
Positional-only parameters can be optional, but the mechanism is<br>
significantly different from positional-or-keyword or keyword-only<br>
parameters. Positional-only parameters don't accept default<br>
values. Instead, positional-only parameters can be specified<br>
in optional "groups". Groups of parameters are surrounded by<br>
square brackets, like so::<br>
<br>
def addch([y, x,] ch, [attr], /):<br>
<br>
Positional-only parameters that are not in an option group are<br>
"required" positional-only parameters. All "required"
positional-only<br>
parameters must be contiguous.<br>
<br>
Parameters in an optional group accept arguments in a group; you<br>
must provide arguments either for all of the them or for none of
them.<br>
Using the example of ``addch()`` above, you could not call
``addch()``<br>
in such a way that ``x`` was specified but ``y`` was not (and vice
versa).<br>
The mapping of positional parameters to optional groups is done<br>
based on fitting the number of parameters to groups. Based on the<br>
above definition, ``addch()`` would assign arguments to parameters<br>
in the following way:<br>
<tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |Number of arguments|Parameter assignment |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |0 |*raises an exception* |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |1 |``ch`` |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |2 |``ch``, ``attr`` |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |3 |``y``, ``x``, ``ch`` |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |4 |``y``, ``x``, ``ch``, ``attr``|</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><tt> |5 or more |*raises an exception* |</tt><tt><br>
</tt><tt> +-------------------+------------------------------+</tt><tt><br>
</tt><br>
<br>
More semantics of positional-only parameters:<br>
<br>
* Although positional-only parameter technically have names,<br>
these names are internal-only; positional-only parameters<br>
are *never* externally addressable by name. (Similarly<br>
to ``*args`` and ``**kwargs``.)<br>
<br>
* It's possible to nest option groups.<br>
<br>
* If there are no required parameters, all option groups behave<br>
as if they're to the right of the required parameter group.<br>
<br>
* For clarity and consistency, the comma for a parameter always<br>
comes immediately after the parameter name. It's a syntax error<br>
to specify a square bracket between the name of a parameter and<br>
the following comma. (This is far more readable than putting<br>
the comma outside the square bracket, particularly for nested<br>
groups.)<br>
<br>
* If there are arguments after the ``/``, then you must specify<br>
a comma after the ``/``, just as there is a comma<br>
after the ``*`` denoting the shift to keyword-only parameters.<br>
<br>
* This syntax has no effect on ``*args`` or ``**kwargs``.<br>
<br>
It's possible to specify a function prototype where the mapping<br>
of arguments to parameters is ambiguous. Consider::<br>
<br>
def range([start,] stop, [range], /):<br>
<br>
Python disambiguates these situations by preferring optional groups<br>
to the *left* of the required group.<br>
<br>
======================<br>
Additional Limitations<br>
======================<br>
<br>
Argument Clinic uses a form of this syntax for specifying<br>
builtins. It imposes further limitations that are<br>
theoretically unnecessary but make the implementation<br>
easier. Specifically:<br>
<br>
* A function that has positional-only parameters currently<br>
cannot have any other kind of parameter. (This will<br>
probably be relaxed slightly in the near future.)<br>
<br>
* Multiple option groups on either side of the required<br>
positional-only parameters must be nested, with the<br>
nesting getting deeper the further away the group is<br>
from the required positional-parameter group.<br>
<br>
Put another way:<br>
all the left-brackets for option groups to the<br>
left of the required group must be specified contiguously,<br>
and<br>
all the right-brackets for option groups to the<br>
right of the required group must be specified contiguously.<br>
<br>
<br>
==============================<br>
Notes For A Future Implementor<br>
==============================<br>
<br>
If we decide to implement positional-only parameters in a future<br>
version of Python, we'd have to do some additional work to preserve<br>
their semantics. The problem: how do we inform a parameter that<br>
no value was passed in for it when the function was called?<br>
<br>
The obvious solution: add a new singleton constant to Python<br>
that is passed in when a parameter is not mapped to an argument.<br>
I propose that the value be called called ``undefined``,<br>
and be a singleton of a special class called ``Undefined``.<br>
If a positional-only parameter did not receive an argument<br>
when called, its value would be set to ``undefined``.<br>
<br>
But this raises a further problem. How do can we tell the<br>
difference between "this positional-only parameter did not<br>
receive an argument" and "the caller passed in ``undefined``<br>
for this parameter"?<br>
<br>
It'd be nice to make it illegal to pass ``undefined`` in<br>
as an argument to a function--to, say, raise an exception.<br>
But that would slow Python down, and the "consenting adults"<br>
rule appears applicable here. So making it illegal should<br>
probably be strongly discouraged but not outright prevented.<br>
<br>
However, it should be allowed (and encouraged) for user<br>
functions to specify ``undefined`` as a default value for<br>
parameters.<br>
<br>
====================<br>
Unresolved Questions<br>
====================<br>
<br>
There are three types of parameters in Python:<br>
<br>
1. positional-only parameters,<br>
2. positional-or-keyword parameters, and<br>
3. keyword-only parameters.<br>
<br>
Python allows functions to have both 2 and 3. And some<br>
builtins (e.g. range) have both 1 and 3. Does it make<br>
sense to have functions that have both 1 and 2? Or<br>
all of the above?<br>
<br>
<br>
======<br>
Thanks<br>
======<br>
<br>
Credit for the use of '/' as the separator between positional-only
and positional-or-keyword<br>
parameters goes to Guido van Rossum, in a proposal from 2012.
[#GUIDO]_<br>
<br>
Credit for making left option groups higher precedence goes to<br>
Nick Coghlan. (Conversation in person at PyCon US 2013.)<br>
<br>
.. [#DICT]<br>
<a class="moz-txt-link-freetext" href="http://docs.python.org/3/library/stdtypes.html#dict">http://docs.python.org/3/library/stdtypes.html#dict</a><br>
<br>
.. [#RANGE]<br>
<a class="moz-txt-link-freetext" href="http://docs.python.org/3/library/functions.html#func-range">http://docs.python.org/3/library/functions.html#func-range</a><br>
<br>
.. [#BORDER]<br>
<a class="moz-txt-link-freetext" href="http://docs.python.org/3/library/curses.html#curses.window.border">http://docs.python.org/3/library/curses.html#curses.window.border</a><br>
<br>
.. [#SENDFILE]<br>
<a class="moz-txt-link-freetext" href="http://docs.python.org/3/library/os.html#os.sendfile">http://docs.python.org/3/library/os.html#os.sendfile</a><br>
<br>
.. [#ADDCH]<br>
<a class="moz-txt-link-freetext" href="http://docs.python.org/3/library/curses.html#curses.window.addch">http://docs.python.org/3/library/curses.html#curses.window.addch</a><br>
<br>
.. [#GUIDO]<br>
Guido van Rossum, posting to python-ideas, March 2012:<br>
<a class="moz-txt-link-freetext" href="http://mail.python.org/pipermail/python-ideas/2012-March/014364.html">http://mail.python.org/pipermail/python-ideas/2012-March/014364.html</a><br>
and<br>
<a class="moz-txt-link-freetext" href="http://mail.python.org/pipermail/python-ideas/2012-March/014378.html">http://mail.python.org/pipermail/python-ideas/2012-March/014378.html</a><br>
and<br>
<a class="moz-txt-link-freetext" href="http://mail.python.org/pipermail/python-ideas/2012-March/014417.html">http://mail.python.org/pipermail/python-ideas/2012-March/014417.html</a><br>
<br>
=========<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
</body>
</html>