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/