
Among my objections to this proposal is introspection: how would that work? The PEP mentions that the text of the expression would be available for introspection, but that doesn't seem very useful. At the very least, the PEP needs to talk about inspect.Signature objects, and how they would support these late-bound function arguments. And in particular, how would you create a Signature object that represents a function with such arguments? What would the default values look like? I think Dave Beazley has a talk somewhere where he dynamically creates objects that implement specific Signatures, I'll try to dig it up and produce an example. For me, it's a show-stopper if you can't support this with late-bound arguments. And I suspect the answer to introspection is going to interact with the desire to create late-bound objects outside of the scope of function arguments and pass them around (as mentioned by MAL). They need to exist stand-alone, outside of function parameters. Eric On 10/23/2021 8:13 PM, Chris Angelico wrote:
Incorporates comments from the thread we just had.
Is anyone interested in coauthoring this with me? Anyone who has strong interest in seeing this happen - whether you've been around the Python lists for years, or you're new and interested in getting involved for the first time, or anywhere in between!
https://www.python.org/dev/peps/pep-0671/
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: 24-Oct-2021
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. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/KR2TML... Code of Conduct: http://python.org/psf/codeofconduct/