Since the function has no parameters and is pre-computed, why force all users to *call* it? The @once decorator could just return the value of calling the function:

def once(func):
    return func()

@once
def pwd():
    return os.getcwd()

print(pwd)

On Sun, Apr 26, 2020 at 7:09 AM Tom Forbes <tom@tomforb.es> wrote:
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

For some related discussion about this idea and lru_cache, please see my thread on discuss.python.org.
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@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/


--
--Guido van Rossum (python.org/~guido)
Pronouns: he/him (why is my pronoun here?)