[Python-ideas] dict.setdefault_call(), or API variations thereupon

Steven D'Aprano steve at pearwood.info
Fri Nov 2 22:49:11 EDT 2018

On Sat, Nov 03, 2018 at 01:15:04AM +0100, Anders Hovmöller wrote:
> > defaultdict:
> >    - takes a zero-argument factory function which is 
> >      unconditionally called when the key is missing.
> > 
> > Did I miss any?
> > 
> > What we don't have is a version of setdefault where the default is 
> > evaluated only on need. That would be a great use-case for Call-By-Name 
> > semantics and thunks, if Python supported such :-)
> Could you explain what the difference is between defaultdicts "factory 
> which is unconditionally called when the key is missing" and "the 
> default is evaluated only on need"? To me it seems that 
> "unconditionally called when the key is missing" is a complex way of 
> saying "called only when needed". I must be missing some nuance here.

Consider the use-case where you want to pass a different default value 
to the dict each time:

    d.setdefault(key, expensive_function(1, 2, 3))
    d.setdefault(key, expensive_function(4, 8, 16))
    d.setdefault(key, expensive_function(10, 100, 1000))

The expensive function is eagerly evaluated each time you call 
setdefault, whether the result is needed or not.

defaultdict won't help, because your factory function takes no 
arguments: there's no way to supply arguments for the factory.

__missing__ won't help, because it only receives the key, not arbitrary 

We can of course subclass dict and give it a method with the semantics 
we want:

    d.my_setdefault(key, expensive_function, args=(1, 2, 3), kw={})

but it would be nicer and more expressive if we could tell the 
interpreter "don't evaluate expensive_function(...) unless you really 
need it".

Other languages have this -- I believe it is called "Call By Need" or 
"Call By Name", depending on the precise details of how it works. I call 
it delayed evaluation, and Python already has it, but only in certain 
special syntactic forms:

    spam and <delayed expression>
    spam or <delayed expression>
    <delayed expression> if condition else <delayed expression>

There are others: e.g. the body of functions, including lambda. But 
functions are kinda heavyweight to make and build and call.


More information about the Python-ideas mailing list