From bc5d20ba06834cf649ab34d8ce332b1f7357fdec Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Sun, 24 Oct 2021 10:02:47 +1100 Subject: [PATCH 1/3] PEP 671: Create a PEP for late-bound argument defaults --- pep-0671.rst | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 pep-0671.rst diff --git a/pep-0671.rst b/pep-0671.rst new file mode 100644 index 00000000000..043a5523a97 --- /dev/null +++ b/pep-0671.rst @@ -0,0 +1,121 @@ +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. + +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: From 6dfa57c053e0644557d4fa9870046c6d1d038d2b Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Sun, 24 Oct 2021 10:06:09 +1100 Subject: [PATCH 2/3] PEP 671: Fix formatting --- pep-0671.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep-0671.rst b/pep-0671.rst index 043a5523a97..00ae3257676 100644 --- a/pep-0671.rst +++ b/pep-0671.rst @@ -26,7 +26,7 @@ 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: +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): @@ -55,7 +55,7 @@ given. Specification ============= -Function default arguments can be defined using the new ``=>`` notation: +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): @@ -68,7 +68,7 @@ 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: +Self-referential expressions will result in UnboundLocalError:: def spam(eggs=>eggs): # Nope @@ -81,7 +81,7 @@ 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: +bound arguments are broadly equivalent to code at the top of the function:: def add_item(item, target=>[]): @@ -96,7 +96,7 @@ 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 ``=>``? + want to end with ``=>``? References From 71ff9d5bbc7c1ea944640ca38fdd302fb6d24d25 Mon Sep 17 00:00:00 2001 From: Chris Angelico Date: Sun, 24 Oct 2021 10:13:06 +1100 Subject: [PATCH 3/3] PEP 671: Add a section listing other syntaxes --- pep-0671.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pep-0671.rst b/pep-0671.rst index 00ae3257676..c439121c98c 100644 --- a/pep-0671.rst +++ b/pep-0671.rst @@ -76,6 +76,25 @@ 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 =================