[Python-Dev] Re: Adding a "call_once" decorator to functools

2020-04-27 Thread Tom Forbes
> Why not? It's a decorator, isn't it? Just make it check for number of 
> arguments at decoration time and return a different object.

It’s not that it’s impossible, but I didn’t think the current implementation 
doesn’t make it easy 
(https://github.com/python/cpython/blob/cecf049673da6a24435acd1a6a3b34472b323c97/Lib/functools.py#L771
 
).
 You’d ideally want to skip creating all these objects and special case 
`user_function` having no parameters, but then you have an issue with 
`cache_info()` being passed `cache_len()`. So maybe it’s simplest to use the 
`cache` dictionary with a single static key, but then you’re not really helping 
much, or avoiding this method altogether, which seemed pretty messy.

The C implementation seemed easier to implement - you could re-use the `cache` 
member 
(https://github.com/python/cpython/blob/cecf049673da6a24435acd1a6a3b34472b323c97/Modules/_functoolsmodule.c#L1192
 
)
 and store the result of the function call, but that also seemed sub-optimal as 
the `root` member doesn’t make much sense to be there.

At least, that was my line of thought. It basically seemed that it would be 
more trouble than it was potentially worth, and it might be better to spend my 
time on `call_once` than special-casing `lru_cache`.

> But presumably we should be making lru_cache thread safe if it isn’t.

lru_cache is indeed thread-safe but it doesn’t guarantee that the wrapped 
method is only called _once_ per unique set of arguments. It apparently just 
ensures that the internal state of the cache is not corrupted by concurrent 
accesses.

> It's unfortunate that cached_property doesn't work at module level

It is indeed, but a solution that works generally in any function defined at 
the module level or not would be good to have.

> On 27 Apr 2020, at 22:55, Steve Dower  wrote:
> 
> On 27Apr2020 2237, t...@tomforb.es wrote:
>> 2. Special casing "lru_cache" to account for zero arity methods isn't 
>> trivial and we shouldn't endorse lru_cache as a way of achieving "call_once" 
>> semantics
> 
> Why not? It's a decorator, isn't it? Just make it check for number of 
> arguments at decoration time and return a different object.
> 
> That way, people can decorate their functions now and get correct behaviour 
> (I assume?) on 3.8 and earlier, and also a performance improvement on 3.9, 
> without having to do any version checking.
> 
> This part could even be written in Python.
> 
>> 3. Implementing a thread-safe (or even non-thread safe) "call_once" method 
>> is non-trivial
> 
> Agree that this is certainly true. But presumably we should be making 
> lru_cache thread safe if it isn't.
> 
>> 4. It complements the lru_cache and cached_property methods currently 
>> present in functools.
> 
> It's unfortunate that cached_property doesn't work at module level (as was 
> pointed out on the other threads - thanks for linking those, BTW).
> 
> Cheers,
> Steve



signature.asc
Description: Message signed with OpenPGP
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/YE672NOPVB3AY2VKTL7JWJFLWAYMRNDK/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-Dev] Re: Adding a "call_once" decorator to functools

2020-04-29 Thread Tom Forbes
Hey Raymond,
Thanks for your input here! A new method wouldn’t be worth adding purely for 
performance reasons then, but there is still an issue around semantics and 
locking.

Should we encourage/document `lru_cache` as the way to do `call_once`? If so, 
then I guess that’s suitable, but people have brought up that it might be hard 
to discover and that it doesn’t actually ensure the function is called once.

The reason I bring this up is that I’ve seen several ad-hoc `call_once` 
implementations recently, and creating one is surprisingly complex for someone 
who’s not that experienced with Python.

So I think there’s room to improve the discoverability of lru_cache as an 
“almost” `call_once` alternative, or room for a dedicated method that might 
re-use bits of the`lru_cache` implementation.

Tom

> On 28 Apr 2020, at 20:51, Raymond Hettinger  
> wrote:
> 
> 
>> t...@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:
> 
>call_once = lru_cache(maxsize=None)
> 
> which would be used like this:
> 
>   @call_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#L870
> ² 
> https://github.com/python/cpython/blob/master/Modules/_functoolsmodule.c#L809
> 
> 
> 
> 
> 
> 
>> 
>> Hello,
>> After a great discussion in python-ideas[1][2] it was suggested that I 
>> cross-post this proposal to python-dev to gather more comments from those 
>> who don't follow python-ideas.
>> 
>> The proposal is to add a "call_once" decorator to the functools module that, 
>> as the name suggests, calls a wrapped function once, caching the result and 
>> returning it with subsequent invocations. The rationale behind this proposal 
>> is that:
>> 1. Developers are using "lru_cache" to achieve this right now, which is less 
>> efficient than it could be
>> 2. Special casing "lru_cache" to account for zero arity methods isn't 
>> trivial and we shouldn't endorse lru_cache as a way of achieving "call_once" 
>> semantics 
>> 3. Implementing a thread-safe (or even non-thread safe) "call_once" method 
>> is non-trivial
>> 4. It complements the lru_cache and cached_property methods currently 
>> present in functools.
>> 
>> The specifics of the method would be:
>> 1. The wrapped method is guaranteed to only be called once when called for 
>> the first time by concurrent threads
>> 2. Only functions with no arguments can be wrapped, otherwise an exception 
>> is thrown
>> 3. There is a C implementation to keep speed parity with lru_cache
>> 
>> I've included a naive implementation below (that doesn't meet any of the 
>> specifics listed above) to illustrate the general idea of the proposal:
>> 
>> ```
>> def call_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
>> ```
>> 
>> I'd welcome any feedback on this proposal, and if the response is favourable 
>> I'd love to attempt to implement it.
>> 
>> 1. 
>> https://mail.python.org/archives/list/python-id...@python.org/thread/5OR3LJO7LOL6SC4OOGKFIVNNH4KADBPG/#5OR3LJO7LOL6SC4OOGKFIVNNH4KADBPG
>> 2. 
>> https://discuss.python.org/t/reduce-the-overhead-of-functools-lru-cache-for-functions-with-no-parameters/3956
>> ___
>> Python-Dev mailing list -- python-dev@python.org
>> To unsubscribe send an email to python-dev-le...@python.org
>> https://mail.python.org/mailman3/lists/python-dev.python.org/
>> Message archived at 
>> https://mail.python.org/archives/list/python-dev@python.org/message/5CFUCM4W3Z36U3GZ6Q3XBLDEVZLNFS63/
>> Code of Conduct: http://python.org/psf/codeofconduct/
> 
___
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
htt