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
Status: Draft Type: Standards Track Created: 28-Nov-2023 Python-Version:
3.14 Post-History: 14-Oct-2023, 17-Jan-2024,

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).

Motivation

Keyword argument syntax can become needlessly repetitive and verbose.

Consider the following call: :

    my_function(
      my_first_variable=my_first_variable,
      my_second_variable=my_second_variable,
      my_third_variable=my_third_variable,
    )

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.

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.

We contend that a simple syntactic sugar used to simplify this common
pattern which would confer numerous benefits:

Encourages use of named arguments

This syntax would encourage the use of named arguments, thereby
increasing readability and reducing bugs from argument transposition.

Reduces verbosity

By minimising visual noise and in some cases lines of code, we can
increase readability.

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.

Highlights arguments not following this pattern

With the current syntax, function calls where many arguments are
forwarded from the local context can make other argument values easy to
miss due to the visual noise. For example:

    add_middleware(
        excluded_urls=excluded_urls,
        server_request=server_request,
        client_request=client_request,
        client_response=client_response,
        span_details=_get_span_details(),
        tracer=tracer,
        meter=meter,
    )

With this syntax, the exceptional arguments become easier to identify:

    add_middleware(
        excluded_urls=,
        server_request=,
        client_request=,
        client_response=,
        span_details=_get_span_details(),
        tracer=,
        meter=,
    )

Applicability to dictionary construction

This syntax can be applied to dictionary construction where a similar
pattern 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.

Specification

We propose to introduce syntactic sugar such that, if the value of a
keyword argument is omitted from a function invocation, the argument's
value is inferred to be the variable matching that name at the
invocation scope.

For example, the function invocation: :

    my_function(my_first_variable=, my_second_variable=, my_third_variable=)

Will be interpreted exactly equivalently to following in existing
syntax: :

    my_function(
      my_first_variable=my_first_variable,
      my_second_variable=my_second_variable,
      my_third_variable=my_third_variable,
    )

If no variable matches that name in the invocation scope, a NameError is
raised in an identical manner as would be with the established expanded
syntax.

This proposal only pertains to function invocations; function
definitions are unaffected by the syntax change. All existing valid
syntax is unchanged.

Backwards Compatibility

Only new syntax is added which was previously syntactically erroneous.
No existing valid syntax is modified. As such, the changes proposed are
fully backwards compatible.

Security Implications

There are no security implications for this change.

Prior Art

Python already possesses a very similar feature in f-string
interpolation where f'{x=}' is effectively expanded to f'x={x}' (see
related GitHub issue).

Several modern languages provide similar features during function
invocation, sometimes referred to as 'punning'. For example:

-   In Ruby, f(x:, y:) is syntactic sugar for f(x: x, y: y). See the
    Ruby 3.1.0 release notes (search for "keyword arguments").
-   In ReasonML, f(~x, ~y) is syntactic sugar for f(~x=x, ~y=y). See the
    ReasonML function documentation (search for "punning").
-   In SystemVerilog, (.mult, .mop1, .data); is syntactic sugar for
    (.mult(mult), .mop1(mop1),  .data(data));. See SystemVerilog
    Implicit Port Connections.
-   In Jakt, f(x, y) is syntactic sugar for f(x: x, y: y). See The Jakt
    programming language.

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).
-   In JavaScript, { x, y } is syntactic sugar for {x: x, y: y}. See
    JavaScript Object Initializer.
-   In Rust, User { x, y } is shorthand for User {x: x, y: y}. See Rust
    Using the Field Init Shorthand.

Applicability

We analysed popular Python libraries from the last few years using this
script to compute:

-   The number of keyword arguments 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 to reduce the need for line wraps.

The purpose of this exercise was to compute statistics about the
prevalence of this pattern and should not be interpreted as a
recommendation that the proposed syntactic sugar should be applied
universally.

  Statistic                                                          polars   fastapi   rich     httpx
  ------------------------------------------------------------------ -------- --------- -------- --------
  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%
  Lines saved                                                        170      35        62       117

Based on this, we note that the f(x=x) keyword argument pattern is
widespread, accounting for anywhere from 15% to just below half of all
keyword argument uses depending on the codebase.

Proposed Syntax

While this feature has been proposed on numerous occasions with several
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[7][8][9]. This is a strong indicator that it is the 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 syntactic sugar. See the Ruby 3.1.0 release notes (search
    for "keyword arguments").
-   The syntax is easy to implement as it is simple syntactic sugar.
-   When compared to the prefix form (see Rejected Ideas), this syntax
    communicates "here is a parameter, go find its argument" which is
    more appropriate given the semantics of named arguments.
-   A poll of Python developers indicates that this is the most popular
    syntax among those proposed.

How to Teach This

To ease the communication of and search for this feature, it may also be
valuable to provide this feature with a name, such as 'keyword argument
shorthand'.

Keen Python developers will likely hear about this feature through
typical information channels, such as newsboards, social media, mailing
lists, online forums, or word of mouth. Many more will encounter this
feature while reading code and noting the omission of the value in a
keyword argument at invocation, violating their expectations. We should
ensure such developers have easy access to documentation that explains
the semantics of this feature and that this documentation is easy to
find when searching. For example, the Python Glossary and Tutorial may
be updated accordingly and reasonable keywords may be used to help with
search discoverability. A StackOverflow question 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=}".

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. Given that this feature is a relatively straightforward
syntactic sugar, it is reasonable that a student who possesses a grasp
of keyword arguments will be able to absorb this concept quickly. This
is evidenced by the success of the f-string syntax as well as similar
features in other languages (see Prior Art).

Rejected Ideas

Many alternative syntaxes have been proposed however no syntax 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.

f(a, b, *, x)

On a few occasions the idea has been floated to borrow the syntax from
keyword-only function definitions.

In favour of this proposal:

-   This syntax is familiar from its use to require keyword-only
    arguments in function definitions.
-   A poll of Python developers indicates that this is the second most
    popular syntax among those proposed.

However, we object that:

-   For any given argument, it is less clear from local context whether
    it is positional or named. The * could easily be missed in a long
    argument list and named arguments may be read as positional or vice
    versa.
-   It is unclear whether keyword arguments for which the value was not
    elided may follow the *. If so, then their relative position will be
    confusingly 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 introduce a new effect which could cause confusion. For
    example, f(a, *x, y) would mean something different than
    f(a, *, x, y).

f(=x)

In favour of this form:

-   The prefix operator is more similar to the established *args and
    **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 of this feature is clearer to
    the reader earlier on.

On the contrary:

-   While the prefix version is visually louder, in practice, there is
    no need for this feature to shout its presence any more than a
    typical named argument. By the time we read to the = it is clear
    that the value is filled in automatically just as the value is clear
    in the typical keyword argument case.
-   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).

f(%x) or f(:x) or f(.x)

Several flavours of this syntax have been proposed with the prefix form
substituting another character for =. However, no such form has gained
traction and the choice of symbol seems arbitrary compared to =.
Additionally, there is less precedent in terms of existing language
features (such as f-string) or other languages (such as Ruby).

Objections

There are only a few hard objections to the introduction of this
syntactic sugar. Most of those not in favour of this feature are in the
camp of 'I wouldn't use it'. However, over the extensive conversations
about this feature, 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 subjective and many community members disagree.
-   A nearly-identical syntax is already established for f-strings.
-   Programmers will, as ever, adjust over time.

The feature is confusing

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.
-   This particular syntactic form has been independently proposed on
    numerous occasions, indicating that it is the most
    obvious[10][11][12].

The feature is not explicit

We recognise that, in an obvious sense, the argument value is 'implicit'
in this proposed syntax. However, we do not think that this is what the
Zen of Python is aiming to discourage.

In the sense that we take the Zen to be referring to, keyword arguments
(for example) are more explicit than positional arguments where the
argument name is omitted and impossible to tell from the local context.
Conversely, the syntactic sugar for integers x += 1 is not more implicit
than x = x + 1 in this sense, even though the variable is omitted from
the right hand side, because it 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.

The feature adds another way of doing things

The same argument can be made against all syntax changes. This is a
simple syntactic sugar, much as x += 1 is sugar for x = x + 1 when x is
an integer. This isn't tantamount to a 'new way' of passing arguments
but a more 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.

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.

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.

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.

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.

As described above, 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

Using a plain text editor

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 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 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.

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.

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.

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 the IDE.

Jump to definition

There are a few possible ways that a 'jump to definition' feature could
be implemented depending on the caret/cursor position.

One option is to:

-   Jump to the argument in the function definition if the caret/cursor
    is on the argument
-   Jump to the definition of the elided variable if the caret/cursor is
    on the character following the = in our proposed syntax

Another, potentially complementary, option would be to expand the syntax
visually on mouseover and enable a Ctrl+Click (or Cmd+Click) to the
definition of the variable.

Highlighting other references

IDEs frequently highlight matching code references to the value at the
current caret/cursor position. With this shorthand syntax, when the
caret/cursor is on the argument name it may be valuable to either:

-   Highlight both references to the argument and its value reflecting
    the fact that this name now refers to both
-   Visually expand the syntax on mouseover (as above) and apply
    established highlighting logic according to the cursor

Rename symbol

There are a few ways that IDEs may wish to support a 'Rename symbol'
feature for 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
-   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.

Reference Implementation

A proposed implementation 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.

References

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

[1] Short form for keyword arguments and dicts (2013)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SQKZ273MYAY5WNIQRGEDLYTKVORVKNEZ/#LXMU22F63VPCF7CMQ4OQRH2CG6H7WCQ6

[2] Keyword arguments self-assignment (2020)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SIMIOC7OW6QKLJOTHJJVNNBDSXDE2SGV/

[3] Shorthand notation of dict literal and function call (2020)
https://discuss.python.org/t/shorthand-notation-of-dict-literal-and-function-call/5697/1

[4] Allow identifiers as keyword arguments at function call site
(extension of PEP 3102?) (2023)
https://discuss.python.org/t/allow-identifiers-as-keyword-arguments-at-function-call-site-extension-of-pep-3102/31677

[5] Shorten Keyword Arguments with Implicit Notation: foo(a=a, b=b) to
foo(.a, .b) (2023)
https://discuss.python.org/t/shorten-keyword-arguments-with-implicit-notation-foo-a-a-b-b-to-foo-a-b/33080

[6] Syntactic sugar to encourage use of named arguments (2023)
https://discuss.python.org/t/syntactic-sugar-to-encourage-use-of-named-arguments/36217

[7] Short form for keyword arguments and dicts (2013)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SQKZ273MYAY5WNIQRGEDLYTKVORVKNEZ/#LXMU22F63VPCF7CMQ4OQRH2CG6H7WCQ6

[8] Keyword arguments self-assignment (2020)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SIMIOC7OW6QKLJOTHJJVNNBDSXDE2SGV/

[9] Syntactic sugar to encourage use of named arguments (2023)
https://discuss.python.org/t/syntactic-sugar-to-encourage-use-of-named-arguments/36217

[10] Short form for keyword arguments and dicts (2013)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SQKZ273MYAY5WNIQRGEDLYTKVORVKNEZ/#LXMU22F63VPCF7CMQ4OQRH2CG6H7WCQ6

[11] Keyword arguments self-assignment (2020)
https://mail.python.org/archives/list/python-ideas@python.org/thread/SIMIOC7OW6QKLJOTHJJVNNBDSXDE2SGV/

[12] Syntactic sugar to encourage use of named arguments (2023)
https://discuss.python.org/t/syntactic-sugar-to-encourage-use-of-named-arguments/36217