On Wed, Apr 29, 2020 at 9:36 PM Raymond Hettinger
<raymond.hettin...@gmail.com> wrote:
> Do you have some concrete examples we could look at?   I'm having trouble 
> visualizing any real use cases and none have been presented so far.

This pattern occurs not infrequently in our Django server codebase at
Instagram. A typical case would be that we need a client object to
make queries to some external service, queries using the client can be
made from various locations in the codebase (and new ones could be
added any time), but there is noticeable overhead to the creation of
the client (e.g. perhaps it does network work at creation to figure
out which remote host can service the needed functionality) and so
having multiple client objects for the same remote service existing in
the same process is waste.

Or another similar case might be creation of a "client" object for
querying a large on-disk data set.

> Presumably, the initialization function would have to take zero arguments,

Right, typically for a globally useful client object there are no
arguments needed, any required configuration is also already available
globally.

> have a useful return value,

Yup, the object which will be used by other code to make network
requests or query the on-disk data set.

> must be called only once,

In our use cases it's more a SHOULD than a MUST. Typically if it were
called two or three times in the process due to some race condition
that would hardly matter. However if it were called anew for every
usage that would be catastrophically inefficient.

> not be idempotent,

Any function like the ones I'm describing can be trivially made
idempotent by initializing a global variable and short-circuit
returning that global if already set. But that's precisely the
boilerplate this utility seeks to replace.

> wouldn't fail if called in two different processes,

Separate processes would each need their own and that's fine.

> can be called from multiple places,

Yes, that's typical for the uses I'm describing.

> and can guarantee that a decref, gc, __del__, or weakref callback would never 
> trigger a reentrant call.

"Guarantee" is too strong, but at least in our codebase use of Python
finalizers is considered poor practice and they are rarely used, and
in any case it would be extraordinarily strange for a finalizer to
make use of an object like this that queries an external resource. So
this is not a practical concern. Similarly it would be very strange
for creation of an instance of a class to call a free function whose
entire purpose is to create and return an instance of that very class,
so reentrancy is also not a practical concern.

> Also, if you know of a real world use case, what solution is currently being 
> used.  I'm not sure what alternative call_once() is competing against.

Currently we typically would use either `lru_cache` or the manual
"cache" using a global variable. I don't think that practically
`call_once` would be a massive improvement over either of those, but
it would be slightly clearer and more discoverable for the use case.

> Do you have any thoughts on what the semantics should be if the inner 
> function raises an exception?  Would a retry be allowed?  Or does call_once() 
> literally mean "can never be called again"?

For the use cases I'm describing, if the method raises an exception
the cache should be left unpopulated and a future call should try
again.

Arguably a better solution for these cases is to push the laziness
internal to the class in question, so it doesn't do expensive or
dangerous work on instantiation but delays it until first use. If that
is done, then a simple module-level instantiation suffices to replace
the `call_once` pattern. Unfortunately in practice we are often
dealing with existing widely-used APIs that weren't designed that way
and would be expensive to refactor, so the pattern continues to be
necessary. (Doing expensive or dangerous work at import time is a
major problem that we must avoid, since it causes every user of the
system to pay that startup cost in time and risk of failure, even if
for their use the object would never be used.)

Carl
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/K73NIHFBXWCM2GUWPVJUNI44TSWASIRD/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to