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 
<https://github.com/django/django/blob/77aa74cb70dd85497dbade6bc0f394aa41e88c94/django/forms/renderers.py#L19>),
 internally in other projects at my workplace, inside the stdlib itself 
<https://github.com/python/cpython/blob/2fa67df605e4b0803e7e3aac0b85d851b4b4e09a/Lib/ipaddress.py#L1324>
 and in numerous other projects. In the first few pages of a Github code search 
<https://github.com/search?l=Python&q=%22functools.lru_cache%22&type=Code> 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

For some related discussion about this idea and lru_cache, please see my thread 
on  
<https://discuss.python.org/t/reduce-the-overhead-of-functools-lru-cache-for-functions-with-no-parameters/3956>discuss.python.org
 <http://discuss.python.org/>.

Attachment: signature.asc
Description: Message signed with OpenPGP

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

Reply via email to