Hey Raymond, Thanks for your input here! A new method wouldn’t be worth adding purely for performance reasons then, but there is still an issue around semantics and locking. Should we encourage/document `lru_cache` as the way to do `call_once`? If so, then I guess that’s suitable, but people have brought up that it might be hard to discover and that it doesn’t actually ensure the function is called once. The reason I bring this up is that I’ve seen several ad-hoc `call_once` implementations recently, and creating one is surprisingly complex for someone who’s not that experienced with Python. So I think there’s room to improve the discoverability of lru_cache as an “almost” `call_once` alternative, or room for a dedicated method that might re-use bits of the`lru_cache` implementation. Tom
On 28 Apr 2020, at 20:51, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
tom@tomforb.es wrote:
I would like to suggest adding a simple “once” method to functools. As the name suggests, this would be a decorator that would call the decorated function, cache the result and return it with subsequent calls.
It seems like you would get just about everything you want with one line:
call_once = lru_cache(maxsize=None)
which would be used like this:
@call_once def welcome(): len('hello')
Using lru_cache like this works but it’s not as efficient as it could be - in every case you’re adding lru_cache overhead despite not requiring it.
You're likely imagining more overhead than there actually is. Used as shown above, the lru_cache() is astonishingly small and efficient. Access time is slightly cheaper than writing d[()] where d={(): some_constant}. The infinite_lru_cache_wrapper() just makes a single dict lookup and returns the value.¹ The lru_cache_make_key() function just increments the empty args tuple and returns it.² And because it is a C object, calling it will be faster than for a Python function that just returns a constant, "lambda: some_constant()". This is very, very fast.
Raymond
¹ https://github.com/python/cpython/blob/master/Modules/_functoolsmodule.c#L87... ² https://github.com/python/cpython/blob/master/Modules/_functoolsmodule.c#L80...
Hello, After a great discussion in python-ideas[1][2] it was suggested that I cross-post this proposal to python-dev to gather more comments from those who don't follow python-ideas.
The proposal is to add a "call_once" decorator to the functools module that, as the name suggests, calls a wrapped function once, caching the result and returning it with subsequent invocations. The rationale behind this proposal is that: 1. Developers are using "lru_cache" to achieve this right now, which is less efficient than it could be 2. Special casing "lru_cache" to account for zero arity methods isn't trivial and we shouldn't endorse lru_cache as a way of achieving "call_once" semantics 3. Implementing a thread-safe (or even non-thread safe) "call_once" method is non-trivial 4. It complements the lru_cache and cached_property methods currently present in functools.
The specifics of the method would be: 1. The wrapped method is guaranteed to only be called once when called for the first time by concurrent threads 2. Only functions with no arguments can be wrapped, otherwise an exception is thrown 3. There is a C implementation to keep speed parity with lru_cache
I've included a naive implementation below (that doesn't meet any of the specifics listed above) to illustrate the general idea of the proposal:
``` def call_once(func): sentinel = object() # in case the wrapped method returns None obj = sentinel @functools.wraps(func) def inner(): nonlocal obj, sentinel if obj is sentinel: obj = func() return obj return inner ```
I'd welcome any feedback on this proposal, and if the response is favourable I'd love to attempt to implement it.
1. https://mail.python.org/archives/list/python-ideas@python.org/thread/5OR3LJO... 2. https://discuss.python.org/t/reduce-the-overhead-of-functools-lru-cache-for-... _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@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/5CFUCM4W... Code of Conduct: http://python.org/psf/codeofconduct/