It's not clear to me why people prefer an extra function which would be exactly equivalent to lru_cache in the expected use case (i.e. decorating a function without arguments). It seems like a good way to cause confusion, especially for beginners. Based on the Zen, there should be one obvious way to do it.
What if the functions requires arguments? How to cache calls with
different arguments? What if some arguments are not hashable?
Then I think lru_cache is perfectly suitable for that use case. `once()` would only be useful if you’re calling a function with no arguments and therefore return a constant value. I originally thought that an exception could be raised if `@once()` was used with a function that accepted arguments, but it might be better to instead simply ignore arguments instead? It could help with some situations where your method accepts a single “self” argument, or another value, that you know will be constant across calls.Why ``functools``? Why not your own library or a package at PyPI. Like
https://pypi.org/project/cachetools/ ?
Because `lru_cache` fits the use case almost perfectly, is available in the stdlib and is very, very fast. As such people are using it like they would use `once()` which to me feels like a good argument to either special case `lru_cache()` to account for this or explicitly add a complimentary `once()` method alongside `lru_cache`. Adding a complimentary method seems better.
On Sun, Apr 26, 2020 at 03:03:16PM +0100, 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 <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()
What if the functions requires arguments? How to cache calls with
different arguments? What if some arguments are not hashable?
Why ``functools``? Why not your own library or a package at PyPI. Like
https://pypi.org/project/cachetools/ ?
> 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/>.
Oleg.
--
Oleg Broytman https://phdru.name/ phd@phdru.name
Programmers don't die, they just GOSUB without RETURN.
_______________________________________________
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/74ZGZRB4JF6EPVB5E7WHL44KZUGEUELV/
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
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/2BDIXRRLGJD3JWNUJCCB227SGCQK2YN5/
Code of Conduct: http://python.org/psf/codeofconduct/