PEP 671: Create a PEP for late-bound argument defaults (#2123)
* PEP 671: Create a PEP for late-bound argument defaults * PEP 671: Fix formatting * PEP 671: Add a section listing other syntaxes
This commit is contained in:
parent
0c0a7e9d65
commit
c398b236a5
|
@ -0,0 +1,140 @@
|
||||||
|
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
|
||||||
|
Post-History:
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
Loading…
Reference in New Issue