On 25.05.20 03:03, Rob Cliffe via Python-ideas wrote:


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.

Actually it was intended to use type annotations a la PEP 585 (using builtin types directly) and hence not requiring explicit specification of the factory:

    @supply_defaults
    def bar(x: list = None, y: dict = None):
        pass

This also has the advantage that the types are visible in the function signature. Sure this works only in a limited number of cases, but the case of mutable defaults seems to be quite prominent, and this solves it at little cost.

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:={}):

What about using `~` instead of `:=`. As a horizontal symbol it has some similarity to `=` and usually "~" denotes proportionality which also allows to make a connection to the use case. For proportionality "x ~ y" means there's a non-zero constant "k" such that "x = k*y" and in the case of defaults it would mean, there's a non-trivial step such that `x = step(y)` (where `step=eval` roughly).

    def bar(x=1, y~[], z ~ {}):

It looks better with spaces around "~" but that's probably a matter of being used to it.

A disadvantage is that `~` is already a unary operator, so one could do this: `def foo(x~~y)`. But how often does this occur anyway?