Hello,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. My rationale for suggesting this addition is twofold:
First: It’s fairly common to use `lru_cache()` to implement this behaviour. We use this inside Django (
example), internally in other projects at my workplace,
inside the stdlib itself and in numerous other projects. In the first few pages of a
Github code search it is fairly easy to find examples, any decorated method with no parameters is using `lru_cache()` like `once()`. 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.
Second: Implementing this in Python, in my opinion, crosses the line of “annoying and non-trivial enough to not want to repeatedly do it”. While a naive (untested) implementation might be:
def 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
While to the people who are likely going to be reading this mailing this the code above is understandable and potentially even somewhat simple. However to a lot of people who might not have had experience with writing decorators or understand sentinel objects and their use the above code might be incomprehensible. A much more common, and in my opinion worse, implementation that I’ve seen is something along the lines of this:
_value = None
def get_value():
nonlocal _value
if _value is None:
_value = some_function()
return _value
Which is not ideal for obvious reasons. And these are not even including a potentially key feature: locking the wrapped function so that it is only called once if it is invoked from multiple threads at once.
So, I’d like to propose adding a `once()` decorator to functools that:
1. Has a C implementation, keeping the speed on-par with `lru_cache()`
2. Ensures that the wrapped function is only called once when invoked by multiple threads