diff --git a/pep-0671.rst b/pep-0671.rst new file mode 100644 index 00000000000..c439121c98c --- /dev/null +++ b/pep-0671.rst @@ -0,0 +1,140 @@ +PEP: 671 +Title: Syntax for late-bound function argument defaults +Author: Chris Angelico +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=): + 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: