On Apr 29, 2020, at 11:15 AM, Tom Forbes <tom@tomforb.es> wrote:
What exactly would the issue be with this:
``` import functools from threading import Lock
def once(func): sentinel = object() cache = sentinel lock = Lock()
@functools.wraps(func) def _wrapper(): nonlocal cache, lock, sentinel if cache is sentinel: with lock: if cache is sentinel: cache = func() return cache
return _wrapper ```
This recipe is the best variant so far and gives us something concrete to talk about :-) Benefits: Guarantees the wrapped function is not called more than once. Restrictions: Only works with zero argument functions. Risks: Any reentrancy or recursion will result in deadlock. Limitations: No instrumentation. No ability to reset or clear. Won't work across multiple processes. It would be nice to look at some compelling use cases. Off hand, I can't think of time when I would have used this decorator. Also, I have a nagging worry that holding a non-reentrant lock across an arbitrary user defined function call is recipe for deadlocks. That's why during code reviews we typically check every single use of Lock() to see if it should have been an RLock(), especially in big systems where GC, __del__, or weakref callbacks can trigger running any code at just about any time. Raymond