[Python-checkins] bpo-36540: Documentation for PEP570 - Python positional only arguments (#13202)

Carol Willing webhook-mailer at python.org
Tue May 28 19:45:36 EDT 2019


https://github.com/python/cpython/commit/b76302ddd0896cb39ce69909349b53db6e7776e2
commit: b76302ddd0896cb39ce69909349b53db6e7776e2
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: Carol Willing <carolcode at willingconsulting.com>
date: 2019-05-28T16:45:32-07:00
summary:

bpo-36540: Documentation for PEP570 - Python positional only arguments (#13202)

* bpo-36540: Documentation for PEP570 - Python positional only arguments

* fixup! bpo-36540: Documentation for PEP570 - Python positional only arguments

* Update reference for compound statements

* Apply suggestions from Carol

Co-Authored-By: Carol Willing <carolcode at willingconsulting.com>

* Update Doc/tutorial/controlflow.rst

Co-Authored-By: Carol Willing <carolcode at willingconsulting.com>

* Add extra bullet point and minor edits

files:
M Doc/c-api/code.rst
M Doc/data/refcounts.dat
M Doc/library/inspect.rst
M Doc/reference/compound_stmts.rst
M Doc/tutorial/controlflow.rst

diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst
index fd3f6919d6d7..e2b0b23335e3 100644
--- a/Doc/c-api/code.rst
+++ b/Doc/c-api/code.rst
@@ -33,7 +33,7 @@ bound into a function.
 
    Return the number of free variables in *co*.
 
-.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *lnotab)
+.. c:function:: PyCodeObject* PyCode_New(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *lnotab)
 
    Return a new code object.  If you need a dummy code object to
    create a frame, use :c:func:`PyCode_NewEmpty` instead.  Calling
diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat
index 213ddcb61fae..aca57a1dae9d 100644
--- a/Doc/data/refcounts.dat
+++ b/Doc/data/refcounts.dat
@@ -236,6 +236,7 @@ PyCode_GetNumFree:PyCodeObject*:co:0:
 
 PyCode_New:PyCodeObject*::+1:
 PyCode_New:int:argcount::
+PyCode_New:int:posonlyargcount::
 PyCode_New:int:kwonlyargcount::
 PyCode_New:int:nlocals::
 PyCode_New:int:stacksize::
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 81824ddc1e54..1cc503a8e94b 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -169,6 +169,9 @@ attributes:
 |           |                   | variables (referenced via |
 |           |                   | a function's closure)     |
 +-----------+-------------------+---------------------------+
+|           | co_posonlyargcount| number of positional only |
+|           |                   | arguments                 |
++-----------+-------------------+---------------------------+
 |           | co_kwonlyargcount | number of keyword only    |
 |           |                   | arguments (not including  |
 |           |                   | \*\* arg)                 |
@@ -724,13 +727,9 @@ function.
       |    Name                | Meaning                                      |
       +========================+==============================================+
       | *POSITIONAL_ONLY*      | Value must be supplied as a positional       |
-      |                        | argument.                                    |
-      |                        |                                              |
-      |                        | Python has no explicit syntax for defining   |
-      |                        | positional-only parameters, but many built-in|
-      |                        | and extension module functions (especially   |
-      |                        | those that accept only one or two parameters)|
-      |                        | accept them.                                 |
+      |                        | argument. Positional only parameters are     |
+      |                        | those which appear before a ``/`` entry (if  |
+      |                        | present) in a Python function definition.    |
       +------------------------+----------------------------------------------+
       | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or |
       |                        | positional argument (this is the standard    |
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index 42fa86476239..bf53cb5a48ab 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -483,8 +483,10 @@ A function definition defines a user-defined function object (see section
    decorators: `decorator`+
    decorator: "@" `dotted_name` ["(" [`argument_list` [","]] ")"] NEWLINE
    dotted_name: `identifier` ("." `identifier`)*
-   parameter_list: `defparameter` ("," `defparameter`)* ["," [`parameter_list_starargs`]]
-                 : | `parameter_list_starargs`
+   parameter_list: `defparameter` ("," `defparameter`)* ',' '/' [',' [`parameter_list_no_posonly`]]
+                 :   | `parameter_list_no_posonly`
+   parameter_list_no_posonly: `defparameter` ("," `defparameter`)* ["," [`parameter_list_starargs`]]
+                            : | `parameter_list_starargs`
    parameter_list_starargs: "*" [`parameter`] ("," `defparameter`)* ["," ["**" `parameter` [","]]]
                           : | "**" `parameter` [","]
    parameter: `identifier` [":" `expression`]
diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst
index cfb9645e0da1..813b7ca93c72 100644
--- a/Doc/tutorial/controlflow.rst
+++ b/Doc/tutorial/controlflow.rst
@@ -519,6 +519,176 @@ and of course it would print:
 Note that the order in which the keyword arguments are printed is guaranteed
 to match the order in which they were provided in the function call.
 
+Special parameters
+------------------
+
+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 may look like:
+
+.. code-block:: none
+
+   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
+         -----------    ----------     ----------
+           |             |                  |
+           |        Positional or keyword   |
+           |                                - Keyword only
+            -- Positional only
+
+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 are
+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)
+   ...
+   >>> def pos_only_arg(arg, /):
+   ...     print(arg)
+   ...
+   >>> def kwd_only_arg(*, arg):
+   ...     print(arg)
+   ...
+   >>> def combined_example(pos_only, /, standard, *, kwd_only):
+   ...     print(pos_only, standard, kwd_only)
+
+
+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 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 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
+
+And the last uses all three calling conventions in the same function
+definition::
+
+   >>> combined_example(1, 2, 3)
+   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'
+
+
+Finally, consider this function definition which has a potential collision between the positional argument ``name``  and ``**kwds`` which has ``name`` as a key::
+
+    def foo(name, **kwds):
+        return 'name' in kwds
+
+There is no possible call that will make it return ``True`` as the keyword ``'name'``
+will always to bind to the first parameter. For example::
+
+    >>> foo(1, **{'name': 2})
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in <module>
+    TypeError: foo() got multiple values for argument 'name'
+    >>>
+
+But using ``/`` (positional only arguments), it is possible since it allows ``name`` as a positional argument and ``'name'`` as a key in the keyword arguments::
+
+    def foo(name, /, **kwds):
+        return 'name' in kwds
+    >>> foo(1, **{'name': 2})
+    True
+
+In other words, the names of positional-only parameters can be used in
+``**kwds`` without ambiguity.
+
+-----
+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 you want the name of the parameters to not be
+  available to the user. This is useful when parameter names have no real
+  meaning, if you want to enforce the order of the arguments when the function
+  is called or if you need to take some positional parameters and arbitrary
+  keywords.
+* Use keyword-only when names have meaning and the function definition is
+  more understandable by being explicit with names or you want to prevent
+  users relying on the position of the argument being passed.
+* For an API, use positional-only to prevent prevent breaking API changes
+  if the parameter's name is modified in the future.
 
 .. _tut-arbitraryargs:
 



More information about the Python-checkins mailing list