PEP 736: Final Draft (#4145)
This commit is contained in:
parent
b8b0e37ea5
commit
96b79107f9
|
@ -2,20 +2,21 @@ PEP: 736
|
|||
Title: Shorthand syntax for keyword arguments at invocation
|
||||
Author: Joshua Bambrick <jbambrick@google.com>,
|
||||
Chris Angelico <rosuav@gmail.com>
|
||||
Discussions-To: https://discuss.python.org/t/pep-736-shorthand-syntax-for-keyword-arguments-at-invocation/43432
|
||||
Discussions-To: https://discuss.python.org/t/pep-736-keyword-argument-shorthand-final-draft/58504
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Created: 28-Nov-2023
|
||||
Python-Version: 3.14
|
||||
Post-History: `14-Oct-2023 <https://discuss.python.org/t/syntactic-sugar-to-encourage-use-of-named-arguments/36217>`__,
|
||||
`17-Jan-2024 <https://discuss.python.org/t/pep-736-shorthand-syntax-for-keyword-arguments-at-invocation/43432>`__,
|
||||
`17-Jul-2024 <https://discuss.python.org/t/pep-736-keyword-argument-shorthand-final-draft/58504>`__,
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes introducing syntactic sugar ``f(x=)`` for the pattern
|
||||
where a named argument is the same as the name of the variable corresponding to
|
||||
its value ``f(x=x)``.
|
||||
This PEP proposes to introduce syntactic sugar ``f(x=)`` for the common pattern
|
||||
where a keyword argument has the same name as that of the variable corresponding
|
||||
to its value ``f(x=x)``.
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
@ -32,25 +33,25 @@ Consider the following call:
|
|||
)
|
||||
|
||||
The case of a keyword argument name matching the variable name of its value is
|
||||
prevalent among Python libraries. This verbosity and redundancy discourages
|
||||
use of named arguments and reduces readability by increasing visual noise.
|
||||
prevalent among Python libraries. This redundancy discourages use of named
|
||||
arguments and reduces readability by increasing visual noise.
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
There are two ways to invoke a function with arguments: by position and by
|
||||
keyword. Keyword arguments confer many benefits by being explicit, thus
|
||||
increasing readability and minimising the risk of inadvertent transposition. On
|
||||
the flipside, positional arguments are often used simply to minimise verbosity
|
||||
and visual noise.
|
||||
keyword. By being explicit, keyword arguments increase readability and
|
||||
minimise the risk of inadvertent transposition. On the flipside, positional
|
||||
arguments are often preferred simply to minimise verbosity and visual noise.
|
||||
|
||||
We contend that a simple syntactic sugar used to simplify this common pattern
|
||||
which would confer numerous benefits:
|
||||
would confer numerous benefits:
|
||||
|
||||
Encourages use of named arguments
|
||||
---------------------------------
|
||||
|
||||
This syntax would encourage the use of named arguments, thereby increasing
|
||||
By reducing the visual noise that established keyword argument syntax can cause,
|
||||
this syntax would encourage the use of named arguments, thereby increasing
|
||||
readability and reducing bugs from argument transposition.
|
||||
|
||||
Reduces verbosity
|
||||
|
@ -65,7 +66,7 @@ Encourages consistent variable names
|
|||
A common problem is that semantically identical variables have different names
|
||||
depending on their contexts. This syntax would encourage authors to use the same
|
||||
variable name when calling a function as the argument name, which would increase
|
||||
consistency of variable names used and hence also readability.
|
||||
consistency of variable names used and hence improve readability.
|
||||
|
||||
Highlights arguments not following this pattern
|
||||
-----------------------------------------------
|
||||
|
@ -104,7 +105,7 @@ frequently occurs (where dictionary keys are identical the names of the
|
|||
variables assigned as their values), ``{"x": x, "y": y}`` or ``dict(x=x, y=y)``.
|
||||
With this feature, this can now also be trivially written as ``dict(x=, y=)``.
|
||||
Whether to further support similar syntax in dictionary literals is an open
|
||||
question out of the scope of this PEP.
|
||||
question beyond the scope of this PEP.
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
@ -168,11 +169,11 @@ sometimes referred to as 'punning'. For example:
|
|||
Beyond function invocation specifically, more languages offer similar features:
|
||||
|
||||
* In OCaml, ``let+ x in …`` is syntactic sugar for ``let+ x = x in …``. See
|
||||
`OCaml Short notation for variable bindings (let-punning) <https://v2.ocaml.org/manual/bindingops.html#ss:letops-punning>`__.
|
||||
`OCaml: Short notation for variable bindings (let-punning) <https://v2.ocaml.org/manual/bindingops.html#ss:letops-punning>`__.
|
||||
* In JavaScript, ``{ x, y }`` is syntactic sugar for ``{x: x, y: y}``. See
|
||||
`JavaScript Object Initializer <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer>`__.
|
||||
`JavaScript: Object Initializer <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer>`__.
|
||||
* In Rust, ``User { x, y }`` is shorthand for ``User {x: x, y: y}``. See
|
||||
`Rust Using the Field Init Shorthand <https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-the-field-init-shorthand-when-variables-and-fields-have-the-same-name>`__.
|
||||
`Rust: Using the Field Init Shorthand <https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-the-field-init-shorthand-when-variables-and-fields-have-the-same-name>`__.
|
||||
|
||||
Applicability
|
||||
=============
|
||||
|
@ -181,7 +182,8 @@ We analysed popular Python libraries from the last few years using
|
|||
`this script <https://gist.github.com/joshuabambrick/a850d0e0050129b9252c748fa06c48b2>`__
|
||||
to compute:
|
||||
|
||||
* The number of keyword arguments were of the form ``f(x=x)`` at invocation.
|
||||
* The number of keyword arguments which were of the form ``f(x=x)`` at
|
||||
invocation.
|
||||
* The percentage of keyword arguments which had the form ``f(x=x)`` at
|
||||
invocation.
|
||||
* The number of lines of code which could be saved by using this syntactic sugar
|
||||
|
@ -192,7 +194,7 @@ this pattern and should not be interpreted as a recommendation that the proposed
|
|||
syntactic sugar should be applied universally.
|
||||
|
||||
===================================================================== =============== ================ ============= ==============
|
||||
Statistic `polars <a_>`__ `fastapi <b_>`__ `rich <c_>`__ `httpx <d_>`__
|
||||
Statistic `Polars <a_>`__ `FastAPI <b_>`__ `Rich <c_>`__ `HTTPX <d_>`__
|
||||
===================================================================== =============== ================ ============= ==============
|
||||
Number of keyword arguments of the form ``f(x=x)`` at invocation 1,654 1,408 566 759
|
||||
Percentage of keyword arguments of the form ``f(x=x)`` at invocation 15.83% 28.11% 15.74% 45.13%
|
||||
|
@ -216,8 +218,8 @@ different forms [1]_ [2]_ [3]_ [4]_ [5]_, [6]_ we have opted to advocate
|
|||
for the ``f(x=)`` form for the following reasons:
|
||||
|
||||
* This feature has been proposed frequently over a ten year period with the
|
||||
``f(x=)`` or ``f(=x)`` being by far the most common syntax [1]_ [2]_ [6]_.
|
||||
This is a strong indicator that it is the obvious notation.
|
||||
``f(x=)`` or ``f(=x)`` being by far the most commonly suggested syntax [1]_
|
||||
[2]_ [6]_. This strongly indicates that it is the most obvious notation.
|
||||
* The proposed syntax closely matches the f-string debug ``f'{var=}'`` syntax
|
||||
(established Pythonic style) and serves an almost identical purpose.
|
||||
* The proposed syntax is exactly analogous to the Ruby keyword argument
|
||||
|
@ -253,11 +255,11 @@ could be written to help explain this feature to those searching for an
|
|||
explanation.
|
||||
|
||||
A teacher may explain this feature to new Python programmers as, "where you see
|
||||
an argument followed by an equals sign, such as ``f(x=)``, this represents a
|
||||
keyword argument where the name of the argument and its value are the same. This
|
||||
can be written equivalently in the expanded notation, ``f(x=x)``." Depending on
|
||||
a student's background, a teacher might further compare this to equivalent
|
||||
syntax in other languages or Python's f-string syntax ``f"{x=}"``.
|
||||
an argument followed only by an equals sign, such as ``f(x=)``, this represents
|
||||
a keyword argument where the name of the argument and its value are the same.
|
||||
This can be written equivalently in the expanded notation, ``f(x=x)``."
|
||||
Depending on a student's background, a teacher might further compare this to
|
||||
equivalent syntax in other languages or to Python's f-string syntax ``f"{x=}"``.
|
||||
|
||||
To understand this, a student of Python would need to be familiar with the
|
||||
basics of functions in addition to the existing keyword argument syntax.
|
||||
|
@ -270,7 +272,7 @@ f-string syntax as well as similar features in other languages (see
|
|||
Rejected Ideas
|
||||
==============
|
||||
|
||||
Many alternative syntaxes have been proposed however no syntax other than
|
||||
Many alternative syntaxes have been proposed however no form other than
|
||||
``f(=x)`` or ``f(x=)`` has garnered significant support. We here enumerate some
|
||||
of the most popular proposed alternatives and why we ultimately reject them.
|
||||
|
||||
|
@ -297,7 +299,7 @@ However, we object that:
|
|||
arbitrary, but if not, then an arbitrary grouping is enforced between
|
||||
different types of keyword arguments and reordering of arguments would be
|
||||
necessary if only one name (the argument or its value) was changed.
|
||||
* The use of ``*`` in function calls is established and this proposal would
|
||||
* The use of ``*`` in function calls is well established and this proposal would
|
||||
introduce a new effect which could cause confusion. For example,
|
||||
``f(a, *x, y)`` would mean something different than ``f(a, *, x, y)``.
|
||||
|
||||
|
@ -310,7 +312,7 @@ In favour of this form:
|
|||
``**kwargs`` syntax for function calls.
|
||||
* It draws more attention to itself when arguments are arranged vertically. In
|
||||
particular, if the arguments are of different lengths it is harder to find the
|
||||
equal sign at the end. Moreover, since Python is read left to right, the use
|
||||
equals sign at the end. Moreover, since Python is read left to right, the use
|
||||
of this feature is clearer to the reader earlier on.
|
||||
|
||||
On the contrary:
|
||||
|
@ -322,7 +324,9 @@ On the contrary:
|
|||
* Semantically, this form communicates 'here is a value, fill in the parameter'
|
||||
which is not what we want to convey.
|
||||
* It is less similar to f-string syntax.
|
||||
* It is less obvious that arbitrary expressions are invalid, e.g. ``f(=a + b)``.
|
||||
* It is less obvious that arbitrary expressions are invalid, for example,
|
||||
``f(=a + b)``, since such expressions are acceptable after the equals sign in
|
||||
the current keyword argument syntax but not before it.
|
||||
|
||||
``f(%x)`` or ``f(:x)`` or ``f(.x)``
|
||||
-----------------------------------
|
||||
|
@ -344,7 +348,7 @@ the following objections were the most common:
|
|||
The syntax is ugly
|
||||
------------------
|
||||
|
||||
This objection is by far the most common. On the contrary, we argue that:
|
||||
This objection is the most common. On the contrary, we argue that:
|
||||
|
||||
* This objection is subjective and many community members disagree.
|
||||
* A nearly-identical syntax is already established for f-strings.
|
||||
|
@ -358,8 +362,9 @@ We argue that:
|
|||
* Introducing new features typically has this impact temporarily.
|
||||
* The syntax is very similar to the established ``f'{x=}'`` syntax.
|
||||
* The feature and syntax are familiar from other popular modern languages.
|
||||
* The expansion of ``x=`` to ``x=x`` is in fact a trivial feature and inherently
|
||||
significantly less complex than ``*arg`` and ``**kwarg`` expansion.
|
||||
* The expansion of ``x=`` to ``x=x`` is a trivial feature and inherently
|
||||
significantly less complex than the popular ``*arg`` and ``**kwarg``
|
||||
expansions.
|
||||
* This particular syntactic form has been independently proposed on numerous
|
||||
occasions, indicating that it is the most obvious [1]_ [2]_ [6]_.
|
||||
|
||||
|
@ -379,9 +384,10 @@ is immediately obvious from the local context what it is.
|
|||
|
||||
The syntax proposed in this PEP is much more closely analogous to the ``x += 1``
|
||||
example (although simpler since we do not propose to introduce a new operation).
|
||||
Moreover, the introduction of this syntactic sugar should encourage the use of
|
||||
keyword arguments over positional ones, making typical Python codebases more
|
||||
explicit in general.
|
||||
Moreover, by removing the barrier of visual noise introduced by the existing
|
||||
keyword argument syntax, this syntactic sugar will encourage the use of keyword
|
||||
arguments over positional ones, making typical Python codebases more explicit in
|
||||
general.
|
||||
|
||||
The feature adds another way of doing things
|
||||
--------------------------------------------
|
||||
|
@ -394,15 +400,15 @@ readable notation for the same way.
|
|||
Renaming the variable in the calling context will break the code
|
||||
----------------------------------------------------------------
|
||||
|
||||
A ``NameError`` would make the mistake clear in most cases. There may be
|
||||
confusion if a variable from a broader scope has the same name as the original
|
||||
variable, so no ``NameError`` would be raised. However, this issue can also
|
||||
occur with keyword arguments using the current syntax (arguably, this syntactic
|
||||
sugar could make it harder to spot). Moreover, having variables with the same
|
||||
name in different scopes is broadly considered bad practice and discouraged by
|
||||
linters.
|
||||
A ``NameError`` would make the mistake clear in the large majority cases. There
|
||||
may be confusion if a variable from a broader scope has the same name as the
|
||||
original variable, so no ``NameError`` would be raised. However, this issue can
|
||||
also occur with keyword arguments using the current syntax (although arguably,
|
||||
this syntactic sugar could make it harder to spot). Moreover, having variables
|
||||
with the same name in different scopes is broadly considered to be bad practice
|
||||
and is discouraged by linters.
|
||||
|
||||
Code editors could highlight the issue based on static analysis - ``f(x=)`` is
|
||||
Code editors could highlight the issue based on static analysis -- ``f(x=)`` is
|
||||
exactly equivalent to writing ``f(x=x)``. If ``x`` does not exist, modern
|
||||
editors have no problem highlighting the issue.
|
||||
|
||||
|
@ -411,28 +417,28 @@ This syntax increases coupling
|
|||
|
||||
We recognise that, as ever, all syntax has the potential for misuse and so
|
||||
should be applied judiciously to improve codebases. In this case, if a parameter
|
||||
and its value have the same semantics in both contexts, that may suggest that
|
||||
using this new syntax is appropriate and will help ameliorate the risk of
|
||||
unintentional desynchronisation which harms readability.
|
||||
and its value have the same semantics in both contexts, that suggests that using
|
||||
this syntax is appropriate and will help ameliorate the risk of unintentional
|
||||
desynchronisation which harms readability.
|
||||
|
||||
However, if the two variables have different semantics, that may suggest that
|
||||
this feature should not be used to encourage consistency or even that they
|
||||
should be renamed.
|
||||
However, if the two variables have different semantics, that suggests that this
|
||||
feature should not be used (since it encourages consistency) or perhaps that one
|
||||
or both of the variables should be renamed.
|
||||
|
||||
Recommendations for using this syntax
|
||||
Recommendations for Using This Syntax
|
||||
=====================================
|
||||
|
||||
As with any other language feature, the programmer should exercise their own
|
||||
judgement about whether it is prudent to use it in any given context. We do not
|
||||
recommend enforcing a rule to use the feature in all cases where it may be
|
||||
applicable.
|
||||
applicable, such as via lint rules or style guides.
|
||||
|
||||
As described `above <This syntax increases coupling>`__, we propose that a
|
||||
reasonable rule of thumb would be to use this in cases where a parameter and its
|
||||
argument have the same semantics in order to reduce unintentional
|
||||
desynchronisation without causing inappropriate coupling.
|
||||
As described in `This syntax increases coupling`_, we propose that a reasonable
|
||||
rule of thumb would be to use this in cases where a parameter and its argument
|
||||
have the same semantics in order to reduce unintentional desynchronisation
|
||||
without causing inappropriate coupling.
|
||||
|
||||
Impact on editing
|
||||
Impact on Editing
|
||||
=================
|
||||
|
||||
Using a plain text editor
|
||||
|
@ -442,7 +448,7 @@ Editing with a plain text editor should generally be unaffected.
|
|||
|
||||
When renaming a variable using a 'Find-Replace' method, where this syntax is
|
||||
used the developer will come across the function argument at invocation (as they
|
||||
would if this syntax was not used). At that point, they can as usual decide
|
||||
would if this syntax was not used). At that point, they can, as usual, decide
|
||||
whether to update the argument as well or expand to the full ``f(x=x)`` syntax.
|
||||
|
||||
As with the current syntax, a 'Find-Replace All' method would fail since the
|
||||
|
@ -450,15 +456,15 @@ keyword argument would not exist at function definition, in the vast majority
|
|||
of cases.
|
||||
|
||||
If the developer leaves the argument name unchanged and forgets to update its
|
||||
value, a ``NameError`` will typically be raised as described
|
||||
`above <Renaming the variable in the calling context will break the code>`__.
|
||||
value, a ``NameError`` will typically be raised as described in
|
||||
`Renaming the variable in the calling context will break the code`_.
|
||||
|
||||
Proposals for IDEs
|
||||
------------------
|
||||
|
||||
In response to community feedback, we include some suggestions regarding how
|
||||
IDEs could handle this syntax. However, we of course defer to the domain experts
|
||||
developing IDEs to use their own discretion.
|
||||
IDEs could handle this syntax. However, we defer to the domain experts
|
||||
developing IDEs to use their discretion.
|
||||
|
||||
Most considerations are made simple by recognising that ``f(x=)`` is just
|
||||
syntactic sugar for ``f(x=x)`` and should be treated the same as at present.
|
||||
|
@ -468,9 +474,9 @@ Highlighting NameErrors
|
|||
|
||||
IDEs typically offer a feature to highlight code that may cause a ``NameError``.
|
||||
We recommend that this syntax be treated similarly to the expanded form
|
||||
``f(x=x)`` to identify and highlight cases where the elided value variable may
|
||||
not exist. What visual cue may be used to highlight these cases may be the same
|
||||
or different from that which would be used with the current syntax, depending on
|
||||
``f(x=x)`` to identify and highlight cases where the elided variable may not
|
||||
exist. What visual cue may be used to highlight these cases may be the same or
|
||||
different from that which would be used with the current syntax, depending on
|
||||
the IDE.
|
||||
|
||||
Jump to definition
|
||||
|
@ -510,18 +516,20 @@ this syntax. For example, if the argument is being renamed, the IDE may:
|
|||
|
||||
* Also rename the variable used as its value in each calling context where this
|
||||
syntax is used
|
||||
* Expand to use the full syntax to pass the variable used as its value
|
||||
* Expand to use the full syntax to pass the unchanged variable as the value of
|
||||
the renamed argument
|
||||
* Prompt the developer to select between the two above options
|
||||
|
||||
The last option here seems most preferable in order to reduce unintentional
|
||||
desynchronisation of names while highlighting the user to the changes.
|
||||
The last option seems to be the most preferable to reduce unintentional
|
||||
desynchronisation of names while highlighting the changes to the programmer.
|
||||
|
||||
Reference Implementation
|
||||
========================
|
||||
|
||||
`A proposed implementation <https://github.com/Hels15/cpython/tree/last-build>`_
|
||||
for cpython has been provided by @Hels15. We will extend this implementation to
|
||||
for CPython has been provided by @Hels15. We will extend this implementation to
|
||||
add an AST node attribute indicating for keywords whether the value was elided.
|
||||
Otherwise the AST will remain unchanged.
|
||||
|
||||
References
|
||||
==========
|
||||
|
|
Loading…
Reference in New Issue