From c13527632fb4b94dca913c29c3db75760fa58084 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 8 May 2019 13:37:48 -0400 Subject: [PATCH 1/6] bpo-36540: Documentation for PEP570 - Python positional only arguments --- Doc/c-api/code.rst | 2 +- Doc/library/inspect.rst | 13 ++- Doc/tutorial/controlflow.rst | 163 +++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 10d89f297c843f..0257cd9269d0e3 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/library/inspect.rst b/Doc/library/inspect.rst index d12f122a57b599..887332aa036795 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/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 905734539c6851..4ddd51f9e6b554 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -519,6 +519,169 @@ 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:: + + 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 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) + ... + >>> 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 "", line 1, in + 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 "", line 1, in + 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 "", line 1, in + 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 "", line 1, in + TypeError: combined_example() got an unexpected keyword argument 'pos_only' + + +Finally, consider this function definition:: + + 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 "", line 1, in + TypeError: foo() got multiple values for argument 'name' + >>> + +But using ``/`` (positional only arguments), this is possible:: + + 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 names do not matter or have no meaning, and there are + only a few arguments which will always be passed in the same order or in case + when you need arbitrary keyword parameters and some positional ones. +* Use keyword-only when names have meaning and the function definition is + more understandable by being explicit with names. .. _tut-arbitraryargs: From 0c42a54e90d4f16390c1fe6443bec64fc6f5e03a Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Fri, 10 May 2019 19:00:24 +0100 Subject: [PATCH 2/6] fixup! bpo-36540: Documentation for PEP570 - Python positional only arguments --- Doc/data/refcounts.dat | 1 + Doc/tutorial/controlflow.rst | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 35527c179f3481..9aa73f41102e73 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/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 4ddd51f9e6b554..898122975a8474 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -528,7 +528,9 @@ 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:: +A function definition may look like: + +.. code-block:: none def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- @@ -591,7 +593,7 @@ markers ``/`` and ``*``:: ... print(pos_only, standard, kwd_only) -The first function definition ``standard_arg``, the most familiar form, +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:: @@ -677,11 +679,14 @@ The use case will determine which parameters to use in the function definition:: 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 or in case - when you need arbitrary keyword parameters and some positional ones. +* 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 positinal parameters and arbitrary + keywords. * Use keyword-only when names have meaning and the function definition is - more understandable by being explicit with names. + more understandable by being explicit with names or you want to prevent + users relying on the position of the argument being passed. .. _tut-arbitraryargs: From 17c70debe987d1c02908117cb9509adbb13e938a Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Tue, 28 May 2019 00:17:47 +0100 Subject: [PATCH 3/6] Update reference for compound statements --- Doc/reference/compound_stmts.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 42fa8647623935..bf53cb5a48ab03 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`] From bfe39b3a93ed1577f3f1a591c57b3bdd8681b043 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Tue, 28 May 2019 23:13:03 +0100 Subject: [PATCH 4/6] Apply suggestions from Carol Co-Authored-By: Carol Willing --- Doc/tutorial/controlflow.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 898122975a8474..96509ecb8d1dde 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -645,12 +645,12 @@ definition:: TypeError: combined_example() got an unexpected keyword argument 'pos_only' -Finally, consider this function definition:: +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' +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}) @@ -659,7 +659,7 @@ will always to bind to the first parameter. For example:: TypeError: foo() got multiple values for argument 'name' >>> -But using ``/`` (positional only arguments), this is possible:: +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 @@ -682,7 +682,7 @@ 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 positinal parameters and arbitrary + 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 From 3f57eea6cd6353ab5e5d55e9330b7b9bcd096f17 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 29 May 2019 00:31:53 +0100 Subject: [PATCH 5/6] Update Doc/tutorial/controlflow.rst Co-Authored-By: Carol Willing --- Doc/tutorial/controlflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 96509ecb8d1dde..76934fbdd87a9a 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -659,7 +659,7 @@ will always to bind to the first parameter. For example:: 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. +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 From d8e4efc97a1594d8c86b67f4588a29c643e23bea Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 29 May 2019 00:37:45 +0100 Subject: [PATCH 6/6] Add extra bullet point and minor edits --- Doc/tutorial/controlflow.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 76934fbdd87a9a..106412c9642d8f 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -557,8 +557,8 @@ 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 +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. @@ -687,6 +687,8 @@ As guidance: * 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: