On Wed, 1 Dec 2021 at 06:19, Chris Angelico <ros...@gmail.com> wrote:
>
> I've just updated PEP 671 https://www.python.org/dev/peps/pep-0671/
> with some additional information about the reference implementation,
> and some clarifications elsewhere.
>
> *PEP 671: Syntax for late-bound function argument defaults*
>
> Questions, for you all:
>
> 1) If this feature existed in Python 3.11 exactly as described, would
> you use it?

Most likely.

---

> 2) Independently: Is the syntactic distinction between "=" and "=>" a
> cognitive burden?

Presented in isolation, like that, no — however I do feel that the
distinguishing character is the at the wrong side of the equals.
Default values may start with a prefix operator (`+`, `-`, `~`), thus
it could be possible to incorrectly interpret the `>` as some sort of
quote/defer prefix operator (or just difficult to spot) when additional
whitespace is lacking. In other words, I think these look a little too
similar:

    def func(arg=-default): ...
    def func(arg=>default): ...

Additionally `=>` would conflict with the proposed alternate lambda
syntax, both cognitively and syntactically — assuming the `=>` form
would be valid everywhere that a lambda expression is currently
(without requiring additional enclosing parentheses). The following is
legal syntax:

    def func(arg: lambda x: x = 42): ...

    # for clarification:
    # func.__defaults__ == (42,)
    # func.__annotations__ == {'arg': <function <lambda> at 0x...>}

It doesn't look promising to place the marker for late bound defaults
on other side of the equals either — causing a syntactical conflict
with comparison operators or assignment operator  (or cognitive
conflict augmented assignment) depending on the choice of character.

This leads me to favour the `@param=default` style and although I
agree with Abe Dillon that this somewhat mimics the `*args` and
`**kwds` syntax, I don't see this parallel as a negative. We already
have some variation of late binding in parameter lists, where? `*args`
and `**kwds`: both are rebound upon each call of the function.

Another odd (though not useful) similarity with the current proposal is
that function objects also lack attributes containing some kind of
special representation of the `*args` and `**kwds` parameter defaults
(i.e. the empty tuple & dict). One **cannot** successfully perform
something akin to the following:

    def func(**kwds):
        return kwds

    func.__kwds_dict_default__ = {'keyword_one': 1}
    assert func() == {'keyword_one': 1}

Just as with the proposal one cannot modify the method(s) of calculation
used to obtain the late bound default(s) once a function is defined.

I don't know that I have a strong preference for the specific marker
character, but I quite like how `@param=default` could be understood
as "at each (call) `param` defaults to `default`".

---

> 3) If "yes" to question 1, would you use it for any/all of (a) mutable
> defaults, (b) referencing things that might have changed, (c)
> referencing other arguments, (d) something else?

Likely all three, maybe all four. A combination of (b) & (c) could be
particularly useful with methods since one of those other arguments is
`self`, for example:

    class IO:
        def truncate(self, position=>self.tell()): ...

---

> 5) Do you know how to compile CPython from source, and would you be
> willing to try this out? Please? :)

I have.

The first unwelcome surprise was:

    >>> def func(a=>[]):
    ...     return a
    ...

    >>> import inspect
    >>> inspect.signature(func).parameters['a'].default
    Ellipsis

Here the current behaviour of returning `Ellipsis` is very unfortunate,
and I think could lead to a lot of head scratching — people wondering
why they are getting ellipses in their code, seemingly from nowhere.
Sure, it can be noted in the official documentation that `Ellipsis` is
used as the indicator of late bound defaults, but third-party resources
which aim to explain the uses of `Ellipsis` would (with their current
content) leave someone clueless.

Additionally I don't think it's too unreasonable an expectation that,
for a function with no required parameters, either of the following (or
something similar) should be equivalent to calling `func()`:

    pos_only_args, kwds = [], {}
    for name, param in inspect.signature(func).parameters.items():
        if param.default is param.empty:
            continue
        elif param.kind is param.POSITIONAL_ONLY:
            pos_only_args.append(param.default)
        else:
            kwds[name] = param.default

    func(*pos_only_args, **kwds)

    # or, by direct access to the dunders

    func(*func.__defaults__, **func.__kwdefaults__)

The presence of the above if statement's first branch (which was
technically unnecessary, since we established for the purpose of this
example all arguments of `func` are optional / have non-empty defaults)
hints that perhaps `inspect.Parameter` should grow another sentinel
attribute similar to `Parameter.empty` — perhaps `Parameter.late_bound`
— to be set as the `default` attribute of applicable `Parameter`
instances (if not also to be used as the sentinel in `__defaults__` &
`__kwdefaults__`, instead of `Ellipsis`).

Even if the above were implemented, then only way to indicate that the
late bound default should be used would still be by omission of that
argument. Thus, if we combine a late bound default with positional-only
arguments e.g.:

    def func(a=>[], b=0, /): ...

It then becomes impossible to programmatically use the given late bound
default for `a` whilst passing a value for `b`. Sure, in this simplistic
case one can manually pass an empty list, but in general — for the same
reasons that it could be "impossible" to evaluate a late bound default
from another context — it would be impossible to manually compute a
replacement value exactly equivalent to the default.

Honestly the circumstances where one may wish to define a function such
as that above seem limited — but it'd be a shame if reverting to use of
a sentinel were required, just in order to have a guaranteed way of
forcing the default behaviour.
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@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/3GHB5WCHOC4QE4CSAWBI7XAY6ULCZHXD/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to