2021-10-23 19:22:22 -04:00
|
|
|
|
PEP: 671
|
|
|
|
|
Title: Syntax for late-bound function argument defaults
|
|
|
|
|
Author: Chris Angelico <rosuav@gmail.com>
|
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 24-Oct-2021
|
|
|
|
|
Python-Version: 3.11
|
2021-10-23 20:13:41 -04:00
|
|
|
|
Post-History: 24-Oct-2021
|
2021-10-23 19:22:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
Function parameters can have default values which are calculated during
|
|
|
|
|
function definition and saved. This proposal introduces a new form of
|
|
|
|
|
argument default, defined by an expression to be evaluated at function
|
|
|
|
|
call time.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
Optional function arguments, if omitted, often have some sort of logical
|
|
|
|
|
default value. When this value depends on other arguments, or needs to be
|
|
|
|
|
reevaluated each function call, there is currently no clean way to state
|
|
|
|
|
this in the function header.
|
|
|
|
|
|
|
|
|
|
Currently-legal idioms for this include::
|
|
|
|
|
|
|
|
|
|
# Very common: Use None and replace it in the function
|
|
|
|
|
def bisect_right(a, x, lo=0, hi=None, *, key=None):
|
|
|
|
|
if hi is None:
|
|
|
|
|
hi = len(a)
|
|
|
|
|
|
|
|
|
|
# Also well known: Use a unique custom sentinel object
|
|
|
|
|
_USE_GLOBAL_DEFAULT = object()
|
|
|
|
|
def connect(timeout=_USE_GLOBAL_DEFAULT):
|
|
|
|
|
if timeout is _USE_GLOBAL_DEFAULT:
|
|
|
|
|
timeout = default_timeout
|
|
|
|
|
|
|
|
|
|
# Unusual: Accept star-args and then validate
|
|
|
|
|
def add_item(item, *optional_target):
|
|
|
|
|
if not optional_target:
|
|
|
|
|
target = []
|
|
|
|
|
else:
|
|
|
|
|
target = optional_target[0]
|
|
|
|
|
|
|
|
|
|
In each form, ``help(function)`` fails to show the true default value. Each
|
|
|
|
|
one has additional problems, too; using ``None`` is only valid if None is not
|
|
|
|
|
itself a plausible function parameter, the custom sentinel requires a global
|
|
|
|
|
constant; and use of star-args implies that more than one argument could be
|
|
|
|
|
given.
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
Function default arguments can be defined using the new ``=>`` notation::
|
|
|
|
|
|
|
|
|
|
def bisect_right(a, x, lo=0, hi=>len(a), *, key=None):
|
|
|
|
|
def connect(timeout=>default_timeout):
|
|
|
|
|
def add_item(item, target=>[]):
|
|
|
|
|
|
|
|
|
|
The expression is saved in its source code form for the purpose of inspection,
|
|
|
|
|
and bytecode to evaluate it is prepended to the function's body.
|
|
|
|
|
|
|
|
|
|
Notably, the expression is evaluated in the function's run-time scope, NOT the
|
|
|
|
|
scope in which the function was defined (as are early-bound defaults). This
|
|
|
|
|
allows the expression to refer to other arguments.
|
|
|
|
|
|
|
|
|
|
Multiple late-bound arguments are evaluated from left to right, and can refer
|
2021-10-24 12:55:51 -04:00
|
|
|
|
to previously-defined values. Order is defined by the function, regardless of
|
|
|
|
|
the order in which keyword arguments may be passed. Using names of other
|
|
|
|
|
arguments is a SyntaxError at function definition time::
|
|
|
|
|
|
|
|
|
|
def spaminate(sausage=>eggs + 1, eggs=>sausage - 1): # SyntaxError
|
|
|
|
|
def selfref(spam=>spam): # SyntaxError
|
|
|
|
|
def frob(n=>len(items), items=[]): # SyntaxError
|
2021-10-23 19:22:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Choice of spelling
|
|
|
|
|
------------------
|
|
|
|
|
|
|
|
|
|
Our chief syntax proposal is ``name=>expression`` -- our two syntax proposals
|
|
|
|
|
... ahem. Amongst our potential syntaxes are::
|
|
|
|
|
|
|
|
|
|
def bisect(a, hi=>len(a)):
|
|
|
|
|
def bisect(a, hi=:len(a)):
|
|
|
|
|
def bisect(a, hi?=len(a)):
|
|
|
|
|
def bisect(a, hi!=len(a)):
|
|
|
|
|
def bisect(a, hi=\len(a)):
|
|
|
|
|
def bisect(a, hi=`len(a)`):
|
|
|
|
|
def bisect(a, hi=@len(a)):
|
|
|
|
|
|
|
|
|
|
Since default arguments behave largely the same whether they're early or late
|
|
|
|
|
bound, the preferred syntax is very similar to the existing early-bind syntax.
|
|
|
|
|
The alternatives offer little advantage over the preferred one.
|
|
|
|
|
|
|
|
|
|
How to Teach This
|
|
|
|
|
=================
|
|
|
|
|
|
|
|
|
|
Early-bound default arguments should always be taught first, as they are the
|
|
|
|
|
simpler and more efficient way to evaluate arguments. Building on them, late
|
|
|
|
|
bound arguments are broadly equivalent to code at the top of the function::
|
|
|
|
|
|
|
|
|
|
def add_item(item, target=>[]):
|
|
|
|
|
|
|
|
|
|
# Equivalent pseudocode:
|
|
|
|
|
def add_item(item, target=<OPTIONAL>):
|
|
|
|
|
if target was omitted: target = []
|
|
|
|
|
|
|
|
|
|
|
2021-10-24 07:48:56 -04:00
|
|
|
|
Interaction with other open PEPs
|
|
|
|
|
================================
|
|
|
|
|
|
|
|
|
|
PEP 661 attempts to solve one of the same problems as this does. It seeks to
|
|
|
|
|
improve the documentation of sentinel values in default arguments, where this
|
|
|
|
|
proposal seeks to remove the need for sentinels in many common cases. PEP 661
|
|
|
|
|
is able to improve documentation in arbitrarily complicated functions (it
|
|
|
|
|
cites ``traceback.print_exception`` as its primary motivation, which has two
|
|
|
|
|
arguments which must both-or-neither be specified); on the other hand, many
|
|
|
|
|
of the common cases would no longer need sentinels if the true default could
|
|
|
|
|
be defined by the function. Additionally, dedicated sentinel objects can be
|
|
|
|
|
used as dictionary lookup keys, where PEP 671 does not apply.
|
|
|
|
|
|
|
|
|
|
|
2021-10-23 19:22:22 -04:00
|
|
|
|
Open Issues
|
|
|
|
|
===========
|
|
|
|
|
|
|
|
|
|
- yield/await? Will they cause problems? Might end up being a non-issue.
|
|
|
|
|
|
|
|
|
|
- annotations? They go before the default, so is there any way an anno could
|
|
|
|
|
want to end with ``=>``?
|
|
|
|
|
|
2021-10-24 12:55:51 -04:00
|
|
|
|
- Rather than banning future refs, these could be permitted, at the price of
|
|
|
|
|
harder-to-explain semantics. Arguments would be resolved first with those
|
|
|
|
|
passed and those with early-bound defaults, and then late-bound ones would
|
|
|
|
|
be evaluated, left-to-right; the consequences for getting it wrong would
|
|
|
|
|
then be UnboundLocalError at call time, rather than SyntaxError from the
|
|
|
|
|
function definition.
|
|
|
|
|
|
2021-10-23 19:22:22 -04:00
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document is placed in the public domain or under the
|
|
|
|
|
CC0-1.0-Universal license, whichever is more permissive.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|