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/>.
I have needed this often enough (and used `lru_cache` ) to say I am full +1 on it. It should not hurt have `functools.once`. On Sun, 26 Apr 2020 at 11:09, 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() 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. _______________________________________________ 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/5OR3LJ... Code of Conduct: http://python.org/psf/codeofconduct/
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.
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 4:26 PM Oleg Broytman <phd@phdru.name> wrote:
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-...
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/74ZGZR... Code of Conduct: http://python.org/psf/codeofconduct/
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. On Sun, Apr 26, 2020 at 6:33 PM Tom Forbes <tom@tomforb.es> wrote:
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 4:26 PM Oleg Broytman <phd@phdru.name> wrote:
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-...
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/74ZGZR... 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/2BDIXR... Code of Conduct: http://python.org/psf/codeofconduct/
I agree, that was the topic of my original post to the python-ideas discussion group[1]. I thought we should special-case `lru_cache()` to account for this use case. The general consensus was that a `once()` decorator would be less confusing and more semantically correct than using `lru_cache` like this. I can also see the reasoning behind that point of view, and it could be further argued that using `lru_cache()` in a non-lru way might be more confusing. Perhaps we could alter the documentation to explicitly “bless” this approach, but as shown in the original python-ideas discussion people had strong negative opinions towards that approach. 1. https://discuss.python.org/t/reduce-the-overhead-of-functools-lru-cache-for-...
On 26 Apr 2020, at 17:43, Alex Hall <alex.mojaki@gmail.com> wrote:
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.
On Sun, Apr 26, 2020 at 6:33 PM Tom Forbes <tom@tomforb.es <mailto:tom@tomforb.es>> wrote:
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/ <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 4:26 PM Oleg Broytman <phd@phdru.name <mailto:phd@phdru.name>> wrote: On Sun, Apr 26, 2020 at 03:03:16PM +0100, Tom Forbes <tom@tomforb.es <mailto: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/77aa74cb70dd85497dbade6bc0f394aa41e88c... <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/2fa67df605e4b0803e7e3aac0b85d851b4b4e... <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 <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/ <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-... <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/> <http://discuss.python.org/ <http://discuss.python.org/>>.
Oleg. -- Oleg Broytman https://phdru.name/ <https://phdru.name/> phd@phdru.name <mailto:phd@phdru.name> Programmers don't die, they just GOSUB without RETURN. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org <mailto:python-ideas@python.org> To unsubscribe send an email to python-ideas-leave@python.org <mailto:python-ideas-leave@python.org> https://mail.python.org/mailman3/lists/python-ideas.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/74ZGZR... <https://mail.python.org/archives/list/python-ideas@python.org/message/74ZGZRB4JF6EPVB5E7WHL44KZUGEUELV/> Code of Conduct: http://python.org/psf/codeofconduct/ <http://python.org/psf/codeofconduct/> _______________________________________________ Python-ideas mailing list -- python-ideas@python.org <mailto:python-ideas@python.org> To unsubscribe send an email to python-ideas-leave@python.org <mailto:python-ideas-leave@python.org> https://mail.python.org/mailman3/lists/python-ideas.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/2BDIXR... <https://mail.python.org/archives/list/python-ideas@python.org/message/2BDIXRRLGJD3JWNUJCCB227SGCQK2YN5/> Code of Conduct: http://python.org/psf/codeofconduct/ <http://python.org/psf/codeofconduct/>
On Sun, Apr 26, 2020 at 6:57 PM Tom Forbes <tom@tomforb.es> wrote:
I agree, that was the topic of my original post to the python-ideas discussion group[1]. I thought we should special-case `lru_cache()` to account for this use case.
The general consensus was that a `once()` decorator would be less confusing and more semantically correct than using `lru_cache` like this. I can also see the reasoning behind that point of view, and it could be further argued that using `lru_cache()` in a non-lru way might be more confusing.
I read the discussion, what I saw was this: Eiffel uses the keyword “ONCE” for this. I would prefer a similar
approach, using a specialised decorator, rather than to special-case lru_cache
- Steven D'Aprano Steven, can you elaborate why you prefer this? Semantically, is this a special case? It seems to follow very naturally from the definition of lru_cache. Is it more special than sum([]), any([]), or all([])? Agreed, a special @once decorator sounds like a better approach here. and later:
but don’t try to make [lru_cache] the “sanctioned” way to do call-once functions. If it’s to be “official”, it should be more discoverable/obvious (i.e., a @once decorator), rather than a side-effect of adding a LRU cache.
- Paul Moore Again, is this a side effect, or the natural expected result? Adding bloat to the API and documentation seems like it will reduce the discoverability of the language overall. Was there anything else in that thread? Do two people make a consensus? Do others agree with this approach? Is there any precedent for something like this in Python? A function which acts just like another function, but only accepts a subset of the arguments? People are going to use lru_cache for this use case regardless of this proposal. A lot of existing code using it is not going to change. Libraries wanting to support 3.8 and below will use it. Many people will never learn about @once. Why not fast-path lru_cache for zero arguments anyway?
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 <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. _______________________________________________ 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/5OR3LJ... Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
On Sun, Apr 26, 2020 at 9:46 AM Alex Hall <alex.mojaki@gmail.com> wrote:
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.
I don't believe it is. lru_cache only guarantees that you will get the same result back for identical arguments, not that the function will only be called once. Seems to me if you call it, then in the middle of caching the value, there's a thread change, you could get to the function wrapped by lru_cache twice (or more times). In order to implement once, it needs to contain a thread lock to ensure its "once" moniker and support the singleton pattern for which it is currently being used (apparently incorrectly) in django and other places. Am I understanding threading correctly here?
This is a good idea but some cases need to be lazily evaluated. Without that property `once()` loses a lot of utility. In the case of Django some of the decorated functions create objects that cannot be instantiated until the Django settings have been loaded, which rules out calling them from the module scope. I believe this use case of lazily initialising objects that cannot be created in the module scope is encountered by other projects. One example off the top of my head might be a database connection, or some other stateful client, that you want a single instance of but want to actually create at some point in the future.
On 26 Apr 2020, at 18:38, Guido van Rossum <guido@python.org> wrote:
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 <mailto: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() 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/>. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org <mailto:python-ideas@python.org> To unsubscribe send an email to python-ideas-leave@python.org <mailto:python-ideas-leave@python.org> https://mail.python.org/mailman3/lists/python-ideas.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/5OR3LJ... <https://mail.python.org/archives/list/python-ideas@python.org/message/5OR3LJO7LOL6SC4OOGKFIVNNH4KADBPG/> Code of Conduct: http://python.org/psf/codeofconduct/ <http://python.org/psf/codeofconduct/>
-- --Guido van Rossum (python.org/~guido <http://python.org/~guido>) Pronouns: he/him (why is my pronoun here?) <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
On Sun, Apr 26, 2020 at 7:47 PM Eric Fahlgren <ericfahlgren@gmail.com> wrote:
On Sun, Apr 26, 2020 at 9:46 AM Alex Hall <alex.mojaki@gmail.com> wrote:
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.
I don't believe it is. lru_cache only guarantees that you will get the same result back for identical arguments, not that the function will only be called once. Seems to me if you call it, then in the middle of caching the value, there's a thread change, you could get to the function wrapped by lru_cache twice (or more times).
Is that desired behaviour? Maybe we should consider changing it, at least for the zero-argument case?
In order to implement once, it needs to contain a thread lock to ensure its "once" moniker and support the singleton pattern for which it is currently being used (apparently incorrectly) in django and other places. Am I understanding threading correctly here?
Should we require everyone else to fix their broken code if we can fix it ourselves? If once() has a thread lock and lru_cache doesn't, I imagine once() might be slower in the single threaded case. Can this be checked? Do we want to leave users deciding between performance and thread safety?
On Sun, Apr 26, 2020 at 1:54 PM Tom Forbes <tom@tomforb.es> wrote:
This is a good idea but some cases need to be lazily evaluated. Without that property `once()` loses a lot of utility. In the case of Django some of the decorated functions create objects that cannot be instantiated until the Django settings have been loaded, which rules out calling them from the module scope.
I believe this use case of lazily initialising objects that cannot be created in the module scope is encountered by other projects. One example off the top of my head might be a database connection, or some other stateful client, that you want a single instance of but want to actually create at some point in the future.
I'm surprised no one mentioned cached_property ( https://docs.python.org/dev/library/functools.html#functools.cached_property) !?
On Apr 26, 2020, at 10:49, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
On Sun, Apr 26, 2020 at 9:46 AM Alex Hall <alex.mojaki@gmail.com> wrote: 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.
I don't believe it is. lru_cache only guarantees that you will get the same result back for identical arguments, not that the function will only be called once. Seems to me if you call it, then in the middle of caching the value, there's a thread change, you could get to the function wrapped by lru_cache twice (or more times). In order to implement once, it needs to contain a thread lock to ensure its "once" moniker and support the singleton pattern for which it is currently being used (apparently incorrectly) in django and other places. Am I understanding threading correctly here?
There are three different use cases for “once” in a threaded program: 1. It’s incorrect or dangerous to even call the function twice. 2. The function isn’t idempotent but you need it to be. 3. The function is idempotent and it’s purely a performance optimization. For the third case, you don’t need any synchronization for correctness (as long as reading and writing the cache value is atomic), and it may actually be a lot faster. Sure, it means occasionally you end up doing the work two or even more times at startup, but in exchange you avoid a zillion thread locks, which can be a lot more expensive. If that’s the case with those Django uses, they’re not using it incorrectly. Also, if you know your app’s sequencing well enough and know exactly what the GIL guarantees, you might be able to prove (or at least convince yourself well enough that if test X passes it’s almost certainly safe) that there’s no chance of startup contention. This includes the really trivial case where you know what’s needed before you fork any threads that might need it (although for a lot of those cases, in Python, it’s probably simpler to just use a module global, but using an unsynchronized cache isn’t terrible for readability). Of course it’s also possible that Django is using it incorrectly and it just shows up as a handful of web apps starting up wrong one in a million instances and there are live bugs all over the internet that nobody’s handling right. But I wouldn’t just assume that it’s incorrect and add a new feature to Python and encourage Django to rewrite a whole lot of code to use it without finding an actual bug first. Also, it’s pretty easy to turn a unsynchronized implementation into a synchronized one: just add a @synchronized decorator around the @lru_cache or @cached_property decorator (or write a simple @synchronized_lru_cache or @synchronized_cached_property decorator and use that). So, does Python really need to include anything in the stdlib to make it easier? (That’s not a rhetorical question; I’m not sure.) On the other hand, if the bugs are actually the second case rather than the first, you can solve that with something faster than a full read-write mutex, but it’s a lot more complicated (and may not even be writeable in Python at all): read-acquire the cache, and if it’s empty, call the function and then compare-and-swap-release the cache, and if the CAS fails that means someone else got there first so discard your value and return theirs. If that comes up a lot and the performance benefit is often worth having, that seems like it should definitely be in the stdlib because people won’t get it right. But I doubt it does. One last thing: the best way to cache an idempotent nullary function with lru_cache is to use maxsize=None. If people are leaving the default 128, maybe the docs need to be improved in some way?
While the implementation is great, including locking to ensure the underlying function is called only once, this only works for properties rather than methods defined in modules (and more generally, any method).
On 26 Apr 2020, at 20:27, Matthew Einhorn <moiein2000@gmail.com> wrote:
On Sun, Apr 26, 2020 at 1:54 PM Tom Forbes <tom@tomforb.es <mailto:tom@tomforb.es>> wrote: This is a good idea but some cases need to be lazily evaluated. Without that property `once()` loses a lot of utility. In the case of Django some of the decorated functions create objects that cannot be instantiated until the Django settings have been loaded, which rules out calling them from the module scope.
I believe this use case of lazily initialising objects that cannot be created in the module scope is encountered by other projects. One example off the top of my head might be a database connection, or some other stateful client, that you want a single instance of but want to actually create at some point in the future.
I'm surprised no one mentioned cached_property (https://docs.python.org/dev/library/functools.html#functools.cached_property) <https://docs.python.org/dev/library/functools.html#functools.cached_property)>!?
On Sun, Apr 26, 2020 at 05:31:22PM +0100, Tom Forbes wrote:
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's better to start by enforcing the "no arguments" restriction and relaxing it later than the relax the restriction from the beginning and then be sorry about it. So raise if the decorated argument takes arguments. If we change our mind, it is easier to add support for arguments later. This may require special-handling of (class|static)methods, or possibly two versions, see `singledispatch` and `singledispatchmethod`. It might also help to see what other languages do, such as Eiffel.
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.
`once` may even end up being a thin wrapper around `lru_cache`. The idea (for me) is to provide a more discoverable, self-descriptive name, with any performance benefit of a simplified implementation being a bonus. -- Steven
On Sun, Apr 26, 2020 at 06:43:06PM +0200, Alex Hall wrote:
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.
Indeed, and if you want to guarantee that a function is executed exactly *once*, then a decorator called *once* is that Obvious Way. How many beginners do you know who even know what a LRU cache is? -- Steven
On Mon, 27 Apr 2020 10:11:01 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
On Sun, Apr 26, 2020 at 06:43:06PM +0200, Alex Hall wrote:
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.
Indeed, and if you want to guarantee that a function is executed exactly *once*, then a decorator called *once* is that Obvious Way.
How many beginners do you know who even know what a LRU cache is?
The same number of beginners who know to look in functools for a decorator called once? The same number of beginners who create multithreaded programs without understanding what they're getting into, or what they've gotten themselves into once they lose the race condition? The same number of beginners who think, "hmm, I wonder whether I have to keep calling this expensive, zero arity function over and over, or there's some standard library function to prevent that?" Yes, I say those things with at least part of my tongue in part of my cheek. Depending on a definition of "beginner," IMO beginners don't run into this use case. Also, a decorator called once is Obvious, *once* (pun intended) you know it's there. Dan
On 04/26/2020 05:11 PM, Steven D'Aprano wrote:
On Sun, Apr 26, 2020 at 06:43:06PM +0200, Alex Hall wrote:
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.
Indeed, and if you want to guarantee that a function is executed exactly *once*, then a decorator called *once* is that Obvious Way.
How many beginners do you know who even know what a LRU cache is?
How many beginners know they want to call a function only once? -- ~Ethan~
On Mon, Apr 27, 2020 at 12:51 PM Ethan Furman <ethan@stoneleaf.us> wrote:
On 04/26/2020 05:11 PM, Steven D'Aprano wrote:
On Sun, Apr 26, 2020 at 06:43:06PM +0200, Alex Hall wrote:
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.
Indeed, and if you want to guarantee that a function is executed exactly *once*, then a decorator called *once* is that Obvious Way.
How many beginners do you know who even know what a LRU cache is?
How many beginners know they want to call a function only once?
Perhaps they'd more ask "how can I calculate this lazily". A function that's called only once is basically a lazily-calculated value. If you combine @property and @once then you actually would have a lazy attribute. ChrisA
On Sun, Apr 26, 2020 at 07:48:10PM -0700, Ethan Furman wrote:
How many beginners do you know who even know what a LRU cache is?
How many beginners know they want to call a function only once?
More than the number who know about LRU caches. Ethan, are you objecting to a self-descriptive name because it is too confusing for beginners and lru_cache isn't? Because that's the argument you seem to be defending. -- Steven
On 04/27/2020 05:09 AM, Steven D'Aprano wrote:
On Sun, Apr 26, 2020 at 07:48:10PM -0700, Ethan Furman wrote:
How many beginners know they want to call a function only once?
More than the number who know about LRU caches.
Ethan, are you objecting to a self-descriptive name because it is too confusing for beginners and lru_cache isn't? Because that's the argument you seem to be defending.
I'm objecting to using "beginners" as the basis for name choices for advanced topics. Aside from that, `once` is a fine name. I'm sure it means that a function can only be defined once, and subsequent definitions will either be ignored or raise, right? >>> @once ... def my_unique_function(...): ... do_something_cool() ... >>> def something_else(): ... pass ... >>> def my_unique_function(...): ... # uh oh, duplicate! ... RuntimeError - duplicate function name detected -- ~Ethan~
On 27 Apr 2020, at 16:01, Ethan Furman <ethan@stoneleaf.us> wrote:
On 04/27/2020 05:09 AM, Steven D'Aprano wrote:
On Sun, Apr 26, 2020 at 07:48:10PM -0700, Ethan Furman wrote:
How many beginners know they want to call a function only once? More than the number who know about LRU caches. Ethan, are you objecting to a self-descriptive name because it is too confusing for beginners and lru_cache isn't? Because that's the argument you seem to be defending.
I'm objecting to using "beginners" as the basis for name choices for advanced topics.
Aside from that, `once` is a fine name. I'm sure it means that a function can only be defined once, and subsequent definitions will either be ignored or raise, right?
@once ... def my_unique_function(...): ... do_something_cool() ... def something_else(): ... pass ... def my_unique_function(...): ... # uh oh, duplicate! ... RuntimeError - duplicate function name detected
That needs a linter to spot ai think. Or are you saying that once could be interested to mean I cannot do that 2nd def? Barry
-- ~Ethan~ _______________________________________________ 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/VD7ZSM... Code of Conduct: http://python.org/psf/codeofconduct/
On 04/27/2020 08:26 AM, Barry wrote:
On 27 Apr 2020, at 16:01, Ethan Furman wrote:
I'm objecting to using "beginners" as the basis for name choices for advanced topics.
Aside from that, `once` is a fine name. I'm sure it means that a function can only be defined once, and subsequent definitions will either be ignored or raise, right?
>>> @once ... def my_unique_function(...): ... do_something_cool() ... >>> def something_else(): ... pass ... >>> def my_unique_function(...): ... # uh oh, duplicate! ... RuntimeError - duplicate function name detected
That needs a linter to spot I think.
Indeed. Linters are great things, although it took me awhile to really appreciate them.
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things. Although, really, what I am saying is that for a beginner to have the thought, "This function always returns the same result, but I can't call it immediately -- I wish there was a way to have it run once and then always return that first result," and go from there to caches and decorators stretches the bounds of credulity past the breaking point. Perhaps we have different meanings for "beginners". -- ~Ethan~
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think? (that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-)) Regards Antoine.
On 04/27/2020 11:34 AM, Antoine Pitrou wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman wrote:
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
I think that's a better name.
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Heh. -- ~Ethan~
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”. Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ 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/NYOB46... Code of Conduct: http://python.org/psf/codeofconduct/
My own opinion is that it's limited enough in scope (and probably uncontroversial implementation-wise) that it doesn't need a PEP. Of course other core developers may disagree, so perhaps wait a few days before submitting a PR :-) Regards Antoine. On Mon, 27 Apr 2020 20:16:53 +0100 Tom Forbes <tom@tomforb.es> wrote:
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”.
Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou <solipsis-xNDA5Wrcr86sTnJN9+BGXg@public.gmane.org> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ Python-ideas mailing list -- python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org To unsubscribe send an email to python-ideas-leave-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.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/NYOB46... Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, Apr 27, 2020 at 12:26 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
My own opinion is that it's limited enough in scope (and probably uncontroversial implementation-wise) that it doesn't need a PEP. Of course other core developers may disagree, so perhaps wait a few days before submitting a PR :-)
Is there a core developer with a particular interest in functools to be consulted? -CHB
Regards
Antoine.
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”.
Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou < solipsis-xNDA5Wrcr86sTnJN9+BGXg@public.gmane.org> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do
That is what I was saying -- that `once`, all by itself, could mean
multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ Python-ideas mailing list --
To unsubscribe send an email to
On Mon, 27 Apr 2020 20:16:53 +0100 Tom Forbes <tom@tomforb.es> wrote: that 2nd def? python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org python-ideas-leave-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.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/NYOB46... 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/BJ7MPR... Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
I'd like to give my explicit +1 on this proposal, and for the suggested alternative name "call_once". To me, this seems to be the case of a common small startup optimization that's very easy to get wrong, and to many users may be too much of a hassle to write their own implementation to be worth using. Thus, it makes sense to include something like this in the standard library. The alternative name "call_once" will make it's purpose significantly more clear. IMO, the parallel to a similar functionality in C++ is a nice added bonus. Although we shouldn't restrict our APIs based on the naming choice of other languages, it's very convenient for those who are already familiar with the other one. Also, I'm in agreement that the addition of this to functools is similar enough to the existing members and small enough that it shouldn't require a PEP. A bpo issue and PR should be sufficient, but I would consider potentially cross-posting this to python-dev to get additional attention from other core developers. On Mon, Apr 27, 2020 at 3:25 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
My own opinion is that it's limited enough in scope (and probably uncontroversial implementation-wise) that it doesn't need a PEP. Of course other core developers may disagree, so perhaps wait a few days before submitting a PR :-)
Regards
Antoine.
On Mon, 27 Apr 2020 20:16:53 +0100 Tom Forbes <tom@tomforb.es> wrote:
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”.
Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou <solipsis-xNDA5Wrcr86sTnJN9+BGXg@public.gmane.org> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ Python-ideas mailing list -- python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org To unsubscribe send an email to python-ideas-leave-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.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/NYOB46... 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/BJ7MPR... Code of Conduct: http://python.org/psf/codeofconduct/
Christopher Barker wrote:
Is there a core developer with a particular interest in functools to be consulted?
Yep, Raymond Hettinger is the designated expert for the functools module. See https://devguide.python.org/experts/#stdlib. I'm not sure as to whether he regularly checks python-ideas, but I suspect he would definitely notice a python-dev thread. Other core developers with some experience in this domain would also likely chime in. On Mon, Apr 27, 2020 at 4:46 PM Christopher Barker <pythonchb@gmail.com> wrote:
On Mon, Apr 27, 2020 at 12:26 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
My own opinion is that it's limited enough in scope (and probably uncontroversial implementation-wise) that it doesn't need a PEP. Of course other core developers may disagree, so perhaps wait a few days before submitting a PR :-)
Is there a core developer with a particular interest in functools to be consulted?
-CHB
Regards
Antoine.
On Mon, 27 Apr 2020 20:16:53 +0100 Tom Forbes <tom@tomforb.es> wrote:
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”.
Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou <solipsis-xNDA5Wrcr86sTnJN9+BGXg@public.gmane.org> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ Python-ideas mailing list -- python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org To unsubscribe send an email to python-ideas-leave-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.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/NYOB46... 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/BJ7MPR... Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, PhD
Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython _______________________________________________ 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/KVXXHF... Code of Conduct: http://python.org/psf/codeofconduct/
Thank you Kyle and everyone else who chimed in here. I’ve made a post to the python-dev mailing list - it has yet to appear, but perhaps it’s in a moderation queue. I’ll ensure it’s posted tomorrow. Tom
On 27 Apr 2020, at 21:56, Kyle Stanley <aeros167@gmail.com> wrote:
Christopher Barker wrote:
Is there a core developer with a particular interest in functools to be consulted?
Yep, Raymond Hettinger is the designated expert for the functools module. See https://devguide.python.org/experts/#stdlib. I'm not sure as to whether he regularly checks python-ideas, but I suspect he would definitely notice a python-dev thread. Other core developers with some experience in this domain would also likely chime in.
On Mon, Apr 27, 2020 at 4:46 PM Christopher Barker <pythonchb@gmail.com> wrote:
On Mon, Apr 27, 2020 at 12:26 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
My own opinion is that it's limited enough in scope (and probably uncontroversial implementation-wise) that it doesn't need a PEP. Of course other core developers may disagree, so perhaps wait a few days before submitting a PR :-)
Is there a core developer with a particular interest in functools to be consulted?
-CHB
Regards
Antoine.
On Mon, 27 Apr 2020 20:16:53 +0100 Tom Forbes <tom@tomforb.es> wrote:
I would think that would be a great name, it’s more explicit and fits more in with its siblings “lru_cache” and “cached_property”.
Not to imply that there is a consensus to move forward, I would be interested in knowing what the next steps would be if there was. Would this require a PEP to be submitted? Or is it similar enough to existing functionality that it might not require one?
On 27 Apr 2020, at 19:37, Antoine Pitrou <solipsis-xNDA5Wrcr86sTnJN9+BGXg@public.gmane.org> wrote:
On Mon, 27 Apr 2020 09:26:53 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
> Or are you saying that once could be interpreted to mean I cannot do that 2nd def?
That is what I was saying -- that `once`, all by itself, could mean multiple things.
That's a good point. A more explicit spelling would be `@call_once`, what do you think?
(that's also the C++ spelling, incidentally, but I suspect most beginners won't care about that ;-))
Regards
Antoine.
_______________________________________________ Python-ideas mailing list -- python-ideas-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.org To unsubscribe send an email to python-ideas-leave-+ZN9ApsXKcEdnm+yROfE0A@public.gmane.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/NYOB46... 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/BJ7MPR... Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, PhD
Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython _______________________________________________ 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/KVXXHF... 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/4WJ42A... Code of Conduct: http://python.org/psf/codeofconduct/
On 28/04/20 6:34 am, Antoine Pitrou wrote:
A more explicit spelling would be `@call_once`, what do you think?
That's not quite right either -- you're allowed to *call* it as many times as you want. It's more like "execute once". But that's a rather long and awkward name. I don't think "once" is a bad name, but we shouldn't kid ourselves that it will be intuitive or discoverable to anyone who isn't familiar with Eiffel. -- Greg
On Mon, Apr 27, 2020 at 07:58:54AM -0700, Ethan Furman wrote:
On 04/27/2020 05:09 AM, Steven D'Aprano wrote:
On Sun, Apr 26, 2020 at 07:48:10PM -0700, Ethan Furman wrote:
How many beginners know they want to call a function only once?
More than the number who know about LRU caches.
Ethan, are you objecting to a self-descriptive name because it is too confusing for beginners and lru_cache isn't? Because that's the argument you seem to be defending.
I'm objecting to using "beginners" as the basis for name choices for advanced topics.
Aside from that, `once` is a fine name. I'm sure it means that a function can only be defined once, and subsequent definitions will either be ignored or raise, right?
Yes, it's annoying when English words can have two or more meanings. The first time I can across math.sin, I was very excited, until I realised it was just the trigonometric function :-( This is the gunslinger.draw versus the artist.draw problem. Unfortunately there can be a conflict between uniqueness and plain English, so unless we start naming our functions using GUIDs instead of words, we're always going to have to deal with it. from 123e4567_e89b_12d3_a456_426655440000 import \ 3a738970_fb72_83c1_a801_93210e19f013 *wink* In the builtins, `range` returns a iterable sequence of integers, it is not the statistical `range` function. `ord` stands for "ordinal", not "order" or "ordinary". `map` doesn't return a geographical map. And somehow we managed to cope. -- Steven
On Tue, Apr 28, 2020 at 12:40:34PM +1200, Greg Ewing wrote:
I don't think "once" is a bad name, but we shouldn't kid ourselves that it will be intuitive or discoverable to anyone who isn't familiar with Eiffel.
I'm not particularly wedded to "once" specifically, but honestly I think that it is silly to say that you have to be familiar with Eiffel to recognise the connection between running a function once and the word "once". https://davidwalsh.name/javascript-once Are JS devs know for their familiarity with Eiffel? :-) At least two other Javascript libraries, Underscore and Rambda, call this function "once": https://stackoverflow.com/a/12713611 JQuery calls a related function "one", although the functional semantics are not quite the same as we're discussing: https://api.jquery.com/one/ Coming back to Python, here's someone who calls their version "run_once": https://stackoverflow.com/questions/50904087/run-a-function-only-once-during... -- Steven
On Sun, Apr 26, 2020 at 09:58:46PM -0400, Dan Sommers wrote:
How many beginners do you know who even know what a LRU cache is?
The same number of beginners who know to look in functools for a decorator called once?
Look in *functools* at all? Probably not that many. Google for "how do I execute a function only once" rather than "lru_cache"? Many, many more. I'm not opposed to jargon when it is appropriate. I don't want to change the name of the lru_cache! But if we have the opportunity for a functional enhancement, why wouldn't we prefer a name which is: - plain English, not jargon - memorable - self-descriptive - describes *what* you want to do rather than *how* you want to do it - and matches the terminology used by other languages and libraries? Possibly "run_once" is even more descriptive. It avoids the "run once versus define once" trap that Ethan mentioned, and is more accurate than the C++ name "call_once". https://stackoverflow.com/questions/50904087/run-a-function-only-once-during... -- Steven
On 28/04/20 1:17 pm, Steven D'Aprano wrote:
I think that it is silly to say that you have to be familiar with Eiffel to recognise the connection between running a function once and the word "once".
The connection is very obvious after you know about it. And probably you can guess the meaning if you see an @once decorator in someone's code. But it's less clear that a person who hasn't seen the word "once" used this way will think of searching for it when they want a way to cache the return value of a function. I know I would be more likely to look for something called "cache" or "memoize". (And I already knew about Eiffel's use of "once"!) -- Greg
On Tue, 28 Apr 2020 11:04:15 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
Yes, it's annoying when English words can have two or more meanings. The first time I can across math.sin, I was very excited, until I realised it was just the trigonometric function :-(
This is the gunslinger.draw versus the artist.draw problem.
It should be solved by the random.draw solution (which calls one or the other, randomly ;-)). Regards Antoine.
On Tue, Apr 28, 2020 at 10:19:49AM +0200, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Tue, 28 Apr 2020 11:04:15 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
Yes, it's annoying when English words can have two or more meanings. The first time I can across math.sin, I was very excited, until I realised it was just the trigonometric function :-(
This is the gunslinger.draw versus the artist.draw problem.
It should be solved by the random.draw solution (which calls one or the other, randomly ;-)).
But only once! :-D
Regards
Antoine.
Oleg. -- Oleg Broytman https://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.
On 28 Apr 2020, at 02:04, Steven D'Aprano <steve@pearwood.info> wrote:
Yes, it's annoying when English words can have two or more meanings. The first time I can across math.sin, I was very excited, until I realised it was just the trigonometric function :-(
@once def my_func(): ... I read that as "at once define my func". That must mean do it now :-) Barry
Actually the draw examples made me think that possibly the @once, @run_once or whatever decorator should possibly add a reset method, as does the lru_cache cache_clear function. Taking the gunslinger draw example it would be: @once def draw(): """ Get your gun """ # Get your gun ... return gun def shoot(targets): for target in targets: gun_in_hand = draw() gun_in_hand.fire_at(target) This would speed up shooting nicely but you would have tied up a valuable resource but it would be really handy to have: def holster_gun(): """ Finished with gun for now """ draw.reset() One of the issues that newcomers to iterators suffer from is the fact that you need to delete and recreate them if you need to re-use them from the beginning however removing and redefining a function is much more complex. Steve Barnes -----Original Message----- From: Oleg Broytman <phd@phdru.name> Sent: 28 April 2020 10:05 To: python-ideas@python.org Subject: [Python-ideas] Re: Adding a "once" function to functools On Tue, Apr 28, 2020 at 10:19:49AM +0200, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Tue, 28 Apr 2020 11:04:15 +1000 Steven D'Aprano <steve@pearwood.info> wrote:
Yes, it's annoying when English words can have two or more meanings. The first time I can across math.sin, I was very excited, until I realised it was just the trigonometric function :-(
This is the gunslinger.draw versus the artist.draw problem.
It should be solved by the random.draw solution (which calls one or the other, randomly ;-)).
But only once! :-D
Regards
Antoine.
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/H35YNU... Code of Conduct: http://python.org/psf/codeofconduct/
On Apr 26, 2020, at 10:41, Guido van Rossum <guido@python.org> wrote:
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()
If that’s all @once does, you don’t need it. Surely this is even clearer: pwd = os.getcwd() The decorator has to add initialization on first demand, or it’s not doing anything. But I think you’re onto something important that everyone else is missing. To the user of this module, this really should look like a variable, not a function. The fact that we want to initialize it later shouldn’t change that. Especially not in Python—other languages bend over backward to make you write getters around every public attribute even when you don’t need any computation; Python bends over backward to let you expose public attributes even when you do need computation. And this isn’t unprecedented. Swift added a lazy variable initialization feature in version 2 even though they already had dispatch_once. And then they discovered that it eliminated nearly all good uses of dispatch_once and deprecated it. All you need is lazy-initialized variables. Your singletons, your possibly-unused expensive tables, your fiddly low-level things with complicated initialization order dependencies, they’re all lazy variables. So what’s left for @once functions that need to look like functions? I think once you think about it in these terms, @lazy makes more sense than @once. The difference between these special attributes and normal ones is that they’re initialized on first demand rather than at definition time. The “on demand” is the salient bit, not the “first”. The only problem is: how could this be implemented? Most of the time you want these things on modules. For lazy imports, the __getattr__ solution of PEP 562 was good enough, but this isn’t nearly as much of an expert feature. Novices write lazy variables in Swift, and if we have to tell them they can’t do it in Python without learning the deep magic of how variable lookup works, that would be a major shame. But I can’t think of an answer that doesn’t run into all the same problems that PEP 562’s competing protocols did.
On Apr 26, 2020, at 7:03 AM, Tom Forbes <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: once = lru_cache(maxsize=None) which would be used like this: @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...
Some libraries implement a 'lazy object' which forwards all operations to a wrapped object, which gets lazily initialised once: https://github.com/ionelmc/python-lazy-object-proxy https://docs.djangoproject.com/en/3.0/_modules/django/utils/functional/ There's also a more general concept of proxying everything to some target. wrapt provides ObjectProxy which is the simplest case, the idea being that you override specific operations: https://wrapt.readthedocs.io/en/latest/wrappers.html Flask and werkzeug provide proxies which forward based on the request being handled, e.g. which thread or greenlet you're in, which allows magic like the global request object: https://flask.palletsprojects.com/en/1.1.x/api/#flask.request All of these have messy looking implementations and hairy edge cases. I imagine the language could be changed to make this kind of thing easier, more robust, and more performant. But I'm struggling to formulate what exactly "this kind of thing is", i.e. what feature the language could use. On Tue, Apr 28, 2020 at 8:02 PM Andrew Barnert via Python-ideas < python-ideas@python.org> wrote:
On Apr 26, 2020, at 10:41, Guido van Rossum <guido@python.org> wrote:
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()
If that’s all @once does, you don’t need it. Surely this is even clearer:
pwd = os.getcwd()
The decorator has to add initialization on first demand, or it’s not doing anything.
But I think you’re onto something important that everyone else is missing. To the user of this module, this really should look like a variable, not a function. The fact that we want to initialize it later shouldn’t change that. Especially not in Python—other languages bend over backward to make you write getters around every public attribute even when you don’t need any computation; Python bends over backward to let you expose public attributes even when you do need computation.
And this isn’t unprecedented. Swift added a lazy variable initialization feature in version 2 even though they already had dispatch_once. And then they discovered that it eliminated nearly all good uses of dispatch_once and deprecated it. All you need is lazy-initialized variables. Your singletons, your possibly-unused expensive tables, your fiddly low-level things with complicated initialization order dependencies, they’re all lazy variables. So what’s left for @once functions that need to look like functions?
I think once you think about it in these terms, @lazy makes more sense than @once. The difference between these special attributes and normal ones is that they’re initialized on first demand rather than at definition time. The “on demand” is the salient bit, not the “first”.
The only problem is: how could this be implemented? Most of the time you want these things on modules. For lazy imports, the __getattr__ solution of PEP 562 was good enough, but this isn’t nearly as much of an expert feature. Novices write lazy variables in Swift, and if we have to tell them they can’t do it in Python without learning the deep magic of how variable lookup works, that would be a major shame. But I can’t think of an answer that doesn’t run into all the same problems that PEP 562’s competing protocols did. _______________________________________________ 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/GHDFHZ... Code of Conduct: http://python.org/psf/codeofconduct/
On Apr 28, 2020, at 12:02, Alex Hall <alex.mojaki@gmail.com> wrote:
Some libraries implement a 'lazy object' which forwards all operations to a wrapped object, which gets lazily initialised once:
https://github.com/ionelmc/python-lazy-object-proxy https://docs.djangoproject.com/en/3.0/_modules/django/utils/functional/
There's also a more general concept of proxying everything to some target. wrapt provides ObjectProxy which is the simplest case, the idea being that you override specific operations:
https://wrapt.readthedocs.io/en/latest/wrappers.html
Flask and werkzeug provide proxies which forward based on the request being handled, e.g. which thread or greenlet you're in, which allows magic like the global request object:
https://flask.palletsprojects.com/en/1.1.x/api/#flask.request
All of these have messy looking implementations and hairy edge cases. I imagine the language could be changed to make this kind of thing easier, more robust, and more performant. But I'm struggling to formulate what exactly "this kind of thing is", i.e. what feature the language could use.
For the case where you’re trying to do the “singleton pattern” for a complex object whose behavior is all about calling specific methods, a proxy might work, and the only thing Python might need, if anything, is ways to make it possible/easier to write a GenericProxy that just delegates everything in some clean way, but even that isn’t really needed if you’re willing to make the proxy specific to the type you’re singleton-ing. But often what you want to lazily initialize is a simple object—a str, a small integer, a list of str, etc. Guido’s example lazily initialized by calling getcwd(), and the first example given for the Swift feature is usually a fullname string built on demand from firstname and lastname. And if you look for examples of @cachedproperty (which really is exactly what you want for @lazy except that it only works for instance attributes, and you want it for class attributes or globals), the singleton pattern seems to be a notable exception, not the usual case; mostly you lazily initialize either simple objects like a str, a pair of floats, a list of int, etc., or numpy/pandas objects. And you can’t proxy either of those in Python. Especially str. Proxies work by duck-typing as the target, but you can’t duck-type as a str, because most builtin and extension functions that want a str ignore its methods and use the PyUnicode API to get directly at its array of characters. Numbers, lists, numpy arrays, etc. aren’t quite as bad as str, but they still have problems. Also, even when it works, the performance cost of a proxy would often be prohibitive. If you write this: @lazy def fullname(): return firstname + " " + lastname … presumably it’s because you need to eliminate the cost of string concatenation every time you need the fullname. But if it then requires every operation on that fullname to go through a dynamic proxy, you’ve probably added more overhead than you saved. So I don’t think proxies are the answer here. Really, we either need descriptors that can somehow work for globals and class attributes (which is probably not solveable), or some brand new language semantics that aren’t built on what’s already there. The latter sounds like probably way more work than this feature deserves, but maybe the experience of Swift argues otherwise.
On Tue, Apr 28, 2020 at 11:45:49AM -0700, Raymond Hettinger wrote:
It seems like you would get just about everything you want with one line:
once = lru_cache(maxsize=None)
But is it thread-safe? If you have a "once"ed expensive function with side-effects, and a second thread calls it before the first thread's initial call has completed, won't you end up executing the function twice? The documentation doesn't mention anything about thread-safety: https://docs.python.org/3/library/functools.html#functools.lru_cache -- Steven
On Apr 28, 2020, at 16:25, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Apr 28, 2020 at 11:45:49AM -0700, Raymond Hettinger wrote:
It seems like you would get just about everything you want with one line:
once = lru_cache(maxsize=None)
But is it thread-safe?
You can add thread safety the same way as any other function: @synchronized @once def spam(): return 42 in a slow and non-thread-safe and non-idempotent way and also launch the missiles the second time we’re called Or wrap a with lock: around the code that calls it, or whatever. Not all uses of once require thread safety. For the really obvious example, imagine you’re sharing a singleton between coroutines instead of threads. And if people are really concerned with the overhead of lru_cache(maxsize=None), the overhead of locking every time you access the value is probably even less acceptable when unnecessary. So, I think it makes sense to leave it up to the user (but to explain the issue in the docs). Or maybe we could add a threading.once (and asyncio.once?) as well as functools.once?
What you want is to acquire a lock if the cache is empty, check if another thread has filled the cache while you where waiting on the lock, call the function, fill the cache and return. I don’t see how you could implement that with two independent decorators without locking all accesses (before `once`) or having the chance that it’s called more than once (after `once`). Tom
On 29 Apr 2020, at 08:15, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Apr 28, 2020, at 16:25, Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Apr 28, 2020 at 11:45:49AM -0700, Raymond Hettinger wrote:
It seems like you would get just about everything you want with one line:
once = lru_cache(maxsize=None)
But is it thread-safe?
You can add thread safety the same way as any other function:
@synchronized @once def spam(): return 42 in a slow and non-thread-safe and non-idempotent way and also launch the missiles the second time we’re called
Or wrap a with lock: around the code that calls it, or whatever.
Not all uses of once require thread safety. For the really obvious example, imagine you’re sharing a singleton between coroutines instead of threads. And if people are really concerned with the overhead of lru_cache(maxsize=None), the overhead of locking every time you access the value is probably even less acceptable when unnecessary.
So, I think it makes sense to leave it up to the user (but to explain the issue in the docs). Or maybe we could add a threading.once (and asyncio.once?) as well as functools.once?
_______________________________________________ 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/RSJLUF... Code of Conduct: http://python.org/psf/codeofconduct/
On 28/04/2020 23:58, Andrew Barnert via Python-ideas wrote:
Really, we either need descriptors that can somehow work for globals and class attributes (which is probably not solveable), or some brand new language semantics that aren’t built on what’s already there. The latter sounds like probably way more work than this feature deserves, but maybe the experience of Swift argues otherwise.
Or you can do it up front, once you have the information to do it with. There aren't that many occasions when lazy evaluation actually wins you anything much; basically when you have moderately expensive information that you may not need at all but will use a lot if you do need it. -- Rhodri James *-* Kynesim Ltd
The connection is very obvious after you know about it. And probably you can guess the meaning if you see an @once decorator in someone's code. But it's less clear that a person who hasn't seen the word "once" used this way will think of searching for it when they want a way to cache the return value of a function. https://docs.python.org/3/library/functools.html#functools.lru_cache
participants (22)
-
Alex Hall
-
Andrew Barnert
-
Antoine Pitrou
-
Barry
-
Barry Scott
-
Chris Angelico
-
Christopher Barker
-
Dan Sommers
-
Eric Fahlgren
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
Joao S. O. Bueno
-
Kyle Stanley
-
Matthew Einhorn
-
Oleg Broytman
-
Raymond Hettinger
-
Rhodri James
-
Steve Barnes
-
Steven D'Aprano
-
Tom Forbes
-
toms.highonstudy@gmail.com