On Mon, May 25, 2020 at 12:33 PM Chris Angelico <ros...@gmail.com> wrote:

> On Tue, May 26, 2020 at 2:24 AM Dominik Vilsmeier
> <dominik.vilsme...@gmx.de> wrote:
> >
> > On 25.05.20 17:29, Ricky Teachey wrote:
> >
> >
> > On Mon, May 25, 2020, 6:49 AM Rob Cliffe via Python-ideas <
> python-ideas@python.org> wrote:
> >>
> >>  (Possibly heretical) Thought:
> >> ISTM that when the decision was made that arg default values should be
> evaluated
> >>         once, at function definition time,
> >> rather than
> >>         every time the function is called and the default needs to be
> supplied
> >> that that was the *wrong* decision.
> >> There may have been what seemed good reasons for it at the time (can
> anyone point me
> >> to any relevant discussions, or is this too far back in the Python
> primeval soup?).
> >> But it is a constant surprise to newbies (and sometimes not-so-newbies).
> >> As is attested to by the number of web pages on this topic.  (Many of
> them defend
> >> the status quo and explain that it's really quite logical - but why
> does the status quo
> >> *need* to be defended quite so vigorously?)
> >
> >
> >
> > First of all: supplying a default object one time and having it start
> fresh at every call would require copying the object. But it is not clear
> what kind of copying of these default values should be done. The language
> doesn't inherently know how to arbitrarily make copies of every object;
> decisions have to be made to define what copying the object would MEAN in
> different contexts.
> >
> > It wouldn't copy the provided default, it would just reevaluate the
> expression. Python has already a way of deferring evaluation, generator
> expressions:
> >
> >     >>> x = 1
> >     >>> g = (x for __ in range(2))
> >     >>> next(g)
> >     1
> >     >>> x = 2
> >     >>> next(g)
> >     2
> >
> > It's like using a generator expression as the default value and then if
> the argument is not provided Python would use `next(gen)` instead of the
> `gen` object itself to fill the missing value. E.g.:
> >
> >     def foo(x = ([] for __ in it.count())):  # if `x` is not provided
> use `next` on that generator
> >         pass
> >
> > Doing this today would use the generator itself to fill a missing `x`,
> so this doesn't buy anything without changing the language.
> >
>
> Well.... if you want to define the semantics that way, there's a way
> cleaner form. Just talk about a lambda function:
>
> def foo(x = lambda: []):
>     pass
>
> and then the function would be called and its return value assigned to
> x, if the parameter isn't given.
>
> But if this were actual language syntax, then it would simply be "the
> expression is evaluated at call time" or something like that.
>
> ChrisA
>

This late binding is what I was clumsily referring to as option *3* (version
with no copying). But this is still going to end up having what people
would consider surprising behavior, is it not?

It is essentially equivalent to this using current syntax:

late_binder = lambda: []

def f(a = None):
    if a is None:
        a = late_binder()

But if you have behavior like this (assuming the := syntax):

_A = []
def f(a:=_A): ...

...which is the same as this:

_A = []
def f(a=None):
    if a is None:
        a = _A

Here's a little bit more fleshed out example:

_complex_mutable_default = MyObj(a = 1, b = 2, c = 3, d = 4, e = 5, f = 6,
g = 7, h = 8, i = 9, j = 10)

def func(x := _complex_mutable_default): ...

Above, _complex_mutable_default is *NOT* immutable-- it is *mutable*.

It gets late-evaluated every call, rather than being evaluated at
definition time. This is going to be pretty surprising for a lot of people.
Of course it won't come up nearly as often as the current def `f(a=[]):
...` issue, but this will eventually need to be explained to people every
bit as much as the "mutable default argument" issue. People will need to
understand they can't do this, and have to do this instead:

_complex_mutable_default_factory = lambda: MyObj(a = 1, b = 2, c = 3, d =
4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10)

def func(x := _complex_mutable_default_factory()): ...

Now, perhaps it will be worth this syntax change to capture a large portion
of more simple cases. But I wonder if people will spend the next decade
complaining about a new category of "surprising behavior" that needs to be
fixed.

Anyway, my opinion: I think things are fine as they are. It is not a heavy
lift to perform the "if <thing> is None:" dance.
_______________________________________________
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/WGMR5IWWGWXZZXXS5E2AMHRAUWSRWVPQ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to