On Sun, Oct 24, 2021 at 1:53 PM Steven D'Aprano <[email protected]> wrote:
>
> On Sun, Oct 24, 2021 at 01:16:02PM +1100, Chris Angelico wrote:
>
> > What I'm more often seeing is cases that are less obviously a
> > late-binding, but where the sentinel is replaced with the "real" value
> > at the point where it's used, rather than up the top of the function.
>
> Got any examples you can share?

Not from the standard library, but try something like this:

def generate_code(secret, timestamp=None):
    ...
    ...
    ...
    ts = (timestamp or now()).to_bytes(8, "big")
    ...

The variable "timestamp" isn't ever actually set to its true default
value, but logically, omitting that parameter means "use the current
time", so this would be better written as:

def generate_code(secret, timestamp=>now()):

That's what I mean by "at the point where it's used" - it's embedded
into the expression that uses it. But the body of a function is its
own business; what matters is the header, which is stating that the
default is None, where the default is really now().

> And is it really a problem if we delay the late-binding to the point
> where the value is actually needed? Here's a toy example:
>
>     # Using only early binding.
>     def function(spam, eggs=None, cheese=None):
>         if eggs is None:
>             eggs = cheap_default()
>         # do stuff using eggs
>         ...
>         if condition:
>             return result
>         if cheese is None:
>             cheese = expensive_default()
>         # do stuff using cheese
>         ...
>         return result
>
>
> The cheese parameter only gets used if the processing of spam with eggs
> fails to give a result. But if cheese is used, the default is expensive.
> Is it really a problem if we delay evaluating that default to the point
> where it is needed?

Delaying evaluation isn't a problem, though it also isn't usually an
advantage. (If the default is truly expensive, then you wouldn't want
to use this, but now we're looking at optimizations, where the
decision is made to favour performance over clarity.)

> The expression is evaluated only if and when the body of the function
> attempts to use the value of arg, if the caller has not provided a
> value. So if the function looks like this:
>
>     # late binding with a thunk that delays execution until needed
>     def func(flag, arg=1/0):
>         if flag:
>             print("Boom!")
>             return arg
>         return None
>
> then func(True) will print Boom! and then raise ZeroDivisionError, and
> func(False) will happily return None.
>
> I have no idea whether thunk-like functionality is workable in Python's
> execution model without slowing down every object reference, but if it
> is possible, there could be other really nice use-cases beyond just
> function defaults.

The biggest problem with thunks is knowing when to trigger evaluation.
We already have functions if you want to be explicit about that:

def func(flag, arg=lambda:1/0):
   ...
      return arg()

so any thunk feature would need some way to trigger its full
evaluation. Should that happen when it gets touched in any way? Or
leave it until some attribute is looked up? What are the consequences
of leaving it unevaluated for too long?

Thunks would be another great feature, but I think they're orthogonal to this.

ChrisA
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/M4IXKTDOP42WHS5YDKNECSB7LWKOZTAM/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to