[Python-Dev] Positional-only parameters in Python

Mario Corchero mariocj89 at gmail.com
Sun Jan 21 08:59:49 EST 2018

Here is the proposal we have worked on (Pablo and I).

I've added Larry as co-author given that a good chunk of the content and
the idea comes from his PEP. Larry, if you don't want to appear as such
please let us know.


Rendered content:

PEP: 9999
Title: Python Positional-Only Parameters
Version: $Revision$
Last-Modified: $Date$
Author: Larry Hastings <larry at hastings.org>, Pablo Galindo
<pablogsal at gmail.com>, Mario Corchero  <mariocj89 at gmail.com>
Discussions-To: Python-Dev <python-dev at python.org>
Content-Type: text/x-rst
Created: 20-Jan-2018


This PEP proposes a syntax for positional-only parameters in Python.
Positional-only parameters are parameters without an externally-usable
name; when a function accepting positional-only parameters is called,
positional arguments are mapped to these parameters based solely on
their position.


Python has always supported positional-only parameters.
Early versions of Python lacked the concept of specifying
parameters by name, so naturally all parameters were
positional-only.  This changed around Python 1.0, when
all parameters suddenly became positional-or-keyword.
This allowed users to provide arguments to a function both
positionally or referencing the keyword used at the definition
of it. But, this is not always desired nor even available as
even in current versions of Python, many CPython
"builtin" functions still only accept positional-only arguments.

Even if positional arguments only in a function can be achieved
via using``*args`` parameters and extracting them one by one,
the solution is far from ideal and not as expressive as the one
proposed in this PEP, which targets to provide syntax to specify
accepting a specific number of positional-only parameters.
Additionally, this will bridge the gap we currently find between
builtin functions that today allows to specify positional-only
parameters and pure Python implementations that lack the
syntax for it.

Positional-Only Parameter Semantics In Current Python

There are many, many examples of builtins that only
accept positional-only parameters.  The resulting
semantics are easily experienced by the Python
programmer--just try calling one, specifying its
arguments by name::

    >>> 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 only positional
via the `/` marker, but this is at the moment only documentational,
Python developers cannot wright such syntax.

In addition, there are some functions with particularly
interesting semantics:

* ``range()``, which accepts an optional parameter
  to the *left* of its required parameter. [#RANGE]_

* ``dict()``, whose mapping/iterator parameter is optional and
  semantically must be positional-only.  Any externally
  visible name for this parameter would occlude
  that name going into the ``**kwarg`` keyword variadic
  parameter dict! [#DICT]_

Obviously one can simulate any of these in pure Python code
by accepting ``(*args, **kwargs)`` and parsing the arguments
by hand.  But this results in a disconnect between the
Python function signature and what it actually accepts,
not to mention the work of implementing said argument parsing.


The new syntax will allow developers to further control how their
api can be consumed. It will allow to restrict the usage of keyword
Specify arguments by adding the new type of positional-only ones.

A similar PEP with a broader scope (PEP 457) was proposed
to define the syntax. This PEP builds on top of part of it
to define and provide an implementation for the ``/`` syntax on
function signatures.

The Current State Of Documentation For Positional-Only Parameters

The documentation for positional-only parameters is incomplete
and inconsistent:

* Some functions denote optional groups of positional-only arguments
  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. [#SENDFILE]_

* Some functions use *both* of the above approaches. [#RANGE]_ [#ADDCH]_

One more important idea to consider: currently in the documentation
there's no way to tell whether a function takes positional-only
parameters.  ``open()`` accepts keyword arguments, ``ord()`` does
not, but there is no way of telling just by reading the
documentation that this is true.

Syntax And Semantics

>From the "ten-thousand foot view", and ignoring ``*args`` and ``**kwargs``
for now, the grammar for a function definition currently looks like this::

    def name(positional_or_keyword_parameters, *, keyword_only_parameters):

Building on that perspective, the new syntax for functions would look
like this::

    def name(positional_only_parameters, /, positional_or_keyword_parameters,
             *, keyword_only_parameters):

All parameters before the ``/`` are positional-only.  If ``/`` is
not specified in a function signature, that function does not
accept any positional-only parameters.
The logic around optional values for positional-only argument
Remains the same as the one for positional-or-keyword. Once
a positional-only argument is provided with a default,
the following positional-only and positional-or-keyword argument
need to have a default as well. Positional-only parameters that
don’t have a default value are "required" positional-only parameters.
Therefore the following are valid signatures::

    def name(p1, p2, /, p_or_kw, *, kw):
    def name(p1, p2=None, /, p_or_kw=None, *, kw):
    def name(p1, p2=None, /, *, kw):
    def name(p1, p2=None, /):
    def name(p1, p2, /, p_or_kw):
    def name(p1, p2, /):

Whilst the followings are not::

    def name(p1, p2=None, /, p_or_kw, *, kw):
    def name(p1=None, p2, /, p_or_kw=None, *, kw):
    def name(p1=None, p2, /):

Full grammar specification

A draft of the proposed grammar specification is::

      tfpdef (',' tfpdef)* ',' '/' [',' [typedargslist]] | typedargslist

      vfpdef (',' vfpdef)* ',' '/' [',' [varargslist]] | varargslist

It will be added to the actual typedargslist and varargslist
but for easier discussion is presented as new_typedargslist and new_varargslist

Possible implementations

Full grammar change as in PEP 3102

This implementation will involve a full change of the Grammar. This will
involve following the steps otlined in PEP 306 [#PEP306]_. In addition, other
steps are needed including:

- Modifying the code object and the function object to be aware of positional
only arguments.

- Modifiying `ceval.c` (`PyEval_EvalCodeEx`, `PyEval_EvalFrameEx`...)
to correctly handle positional-only arguments.

- Modifying `marshal.c` to account for the modifications of the code object.

This does not intent to be a guide or a comprehensive recipe on how to implement
this but a rough outline on the changes this will make to the codebase.

The advantages of this implementation involve speed, consistency with the
implementation of keyword-only parameters as in PEP 3102 and a simpler
of all the tools and modules that will be inpacted by this change.


The following alternatives were discarded along this PEP

Do Nothing

Always an option, just don't adding it. It was considered
though that the benefits of adding it is worth the complexity
it adds to the language.

After marker proposal

A complain the approach has is the fact that the modifier of
the signature impacts the "already passed" tokens.

This might make confusing to "human parsers" to read functions
with many arguments. Example::

  def really_bad_example_of_a_python_function(fist_long_argument,
                                              third_long_argument, /):

It is not until you reach the end of the signature that the reader
realized the ``/`` and therefore the fact that the arguments are
position-only. This deviates from how the keyword-only marker works.

That said we could not find an implementation that would modify the
arguments after the marker, as that will force the one before the
marker to be position only as well. Example::

  def (x, y, /, z):

If we define that ``/`` makes only z position-only it won't be possible
to call x and y via keyword argument. Finding a way to work around it
will add confusion given that at the moment keyword arguments cannot be
followed by positional arguments. ``/`` will therefore make both the
preceding and following position-only.

Per argument marker

Using a per argument marker might be an option as well. The approach
basically adds a token to each of the arguments that are position only
and requires those to be placed together. Example::

  def (.arg1, .arg2, arg3):

Note the dot on arg1 and arg2. Even if this approach might look easier
to read it has been discarded as ``/`` goes further inline with the
keyword-only approach and is less error prone.

Using decorators

It has been sugested in python-ideas [#python-ideas-decorator-based]_ to provide
a decorator written in Python as an implementation for this feature.
This approach
has the advantage that keeps parameter declaration more easy to read but also
introduces an asymetry on how parameter behavior is declared. Also, as the `\`
syntax is already introduced for C functions, this inconsistency will make more
diffcult to implement all tools and modules that deal with this syntax including
but not limited to, the argument clinic, the inspect module and the ast module.
Another disadvantage of this approach is that calling the decorated functions
will be slower than the functions generated if the feature was
implemented directly
in C.


Credit for most of the content of this PEP is contained in Larry
Hastings’s PEP 457.

Credit for the use of '/' as the separator between positional-only and
parameters goes to Guido van Rossum, in a proposal from 2012. [#GUIDO]_

Credit for making left option groups higher precedence goes to
Nick Coghlan. (Conversation in person at PyCon US 2013.)

Credit for discussion about the simplification of the grammar goes to
Braulio Valdivieso.

.. [#DICT]

.. [#RANGE]

.. [#BORDER]


.. [#ADDCH]

.. [#GUIDO]
   Guido van Rossum, posting to python-ideas, March 2012:

.. [#PEP306]

.. [#python-ideas-decorator-based]


This document has been placed in the public domain.

On 20 January 2018 at 15:56, Guido van Rossum <guido at python.org> wrote:

> On Sat, Jan 20, 2018 at 1:25 AM, Mario Corchero <mariocj89 at gmail.com>
> wrote:
>> OK, if no one has anything against, Pablo and I can start a PEP just for
>> the ‘/‘ simple syntax (without the argument group part).
> Go for it!
> Note that your target will be Python 3.8.
> --
> --Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20180121/c96e2065/attachment.html>

More information about the Python-Dev mailing list