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.
|
|
|
|
|
|
|
|
|
|
Self-referential expressions will result in UnboundLocalError::
|
|
|
|
|
|
|
|
|
|
def spam(eggs=>eggs): # Nope
|
|
|
|
|
|
|
|
|
|
Multiple late-bound arguments are evaluated from left to right, and can refer
|
|
|
|
|
to previously-calculated values. Order is defined by the function, regardless
|
|
|
|
|
of the order in which keyword arguments may be passed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ``=>``?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|