On 24/05/2020 21:03, Dominik Vilsmeier wrote:

On 24.05.20 18:34, Alex Hall wrote:


OK, let's forget the colon. The point is just to have some kind of 'modifier' on the default value to say 'this is evaluated on each function call', while still having something that looks like `arg=<default>`. Maybe something like:

     def func(options=from {}):

It looks like the most common use case for this is to deal with mutable defaults, so what is needed is some way to specify a default factory, similar to `collections.defaultdict(list)` or `dataclasses.field(default_factory=list)`. This can be handled by a decorator, e.g. by manually supplying the factories or perhaps inferring them from type annotations:

    @supply_defaults
    def foo(x: list = None, y: dict = None):
        print(x, y)  # [], {}


    @supply_defaults(x=list, y=dict)
    def bar(x=None, y=None):
        print(x, y)  # [], {}


That's very clever, but if you compare it with the status quo:

    def bar(x=None, y=None):
        if x is None: x = []
        if y is None: y={}

it doesn't save a lot of typing and will be far more obscure to newbies
who may not know about decorators.  (And if other decorators are used on the
same function, the fog only thickens.)  They may not know why the contorted way
of assigning to x and y is necessary, but they can follow what it does.
Even someone familiar with decorators will have to look up supply_defaults and
find out what it does (and it jolly well ought to be copiously documented :-)).

Trying to do some blue-sky thinking, I have tried to come up with other ideas,
but have not found anything very convincing, e.g.:

Idea: Invent a new kind of string which behave like f-strings, called say g-strings.
          Add a rule that if the default value of an argument is (an expression containing) a g-string,
          the default value is recalculated every time the function is called and a value for that argument is not passed.
          So:
             def bar(x=g'{ [] }], y=g'{ {} }'):
Cons: I could list a couple myself, no doubt others can think of many more.

No, forget fudges.
I think what is needed is to take the bull by the horns and add some *new syntax*
that says "this default value should be (re)calculated every time it is needed".
Personally I don't think the walrus operator is too bad:
    def bar(x:=[], y:={}):
It's concise, and arguably will largely only confuse people who are already confused.
It AKAICS introduces no ambiguity (it's currently a syntax error).
It *is* somewhat arbitrary (in one context the walrus means 'this assignment becomes an expression',
in another it means 'recalculate this expression every time it's needed').
But hey, you could say the same about colon, comma, parentheses, braces and what-have-you.
It is IMO practical.
It needs no new keywords.
Other syntaxes are of course possible, but for me, in this case, conciseness is a virtue.  YMMV.

(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?)

I realise that it's far too late to change this behaviour in Python 3.
And also that there are uses for the current behaviour.  (Though I can't recall having used it myself.)
But maybe for Python 4?