diff options
author | Carl Meyer <carl@oddbird.net> | 2018-08-28 07:11:56 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2018-08-28 07:11:56 (GMT) |
commit | d658deac6060ee92b449a3bf424b460eafd99f3e (patch) | |
tree | b20a3a288b17dd17fbd5e555f139b2c1df80bcb7 /Lib/functools.py | |
parent | 216b745eafa7cd4a683a8405dcfbd7f5567f504c (diff) | |
download | cpython-d658deac6060ee92b449a3bf424b460eafd99f3e.zip cpython-d658deac6060ee92b449a3bf424b460eafd99f3e.tar.gz cpython-d658deac6060ee92b449a3bf424b460eafd99f3e.tar.bz2 |
bpo-21145: Add cached_property decorator in functools (#6982)
Robust caching of calculated properties is
harder than it looks at first glance, so add
a solid, well-tested implementation to the
standard library.
Diffstat (limited to 'Lib/functools.py')
-rw-r--r-- | Lib/functools.py | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index b3428a4..51048f5 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -868,3 +868,58 @@ class singledispatchmethod: @property def __isabstractmethod__(self): return getattr(self.func, '__isabstractmethod__', False) + + +################################################################################ +### cached_property() - computed once per instance, cached as attribute +################################################################################ + +_NOT_FOUND = object() + + +class cached_property: + def __init__(self, func): + self.func = func + self.attrname = None + self.__doc__ = func.__doc__ + self.lock = RLock() + + def __set_name__(self, owner, name): + if self.attrname is None: + self.attrname = name + elif name != self.attrname: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.attrname!r} and {name!r})." + ) + + def __get__(self, instance, owner): + if instance is None: + return self + if self.attrname is None: + raise TypeError( + "Cannot use cached_property instance without calling __set_name__ on it.") + try: + cache = instance.__dict__ + except AttributeError: # not all objects have __dict__ (e.g. class defines slots) + msg = ( + f"No '__dict__' attribute on {type(instance).__name__!r} " + f"instance to cache {self.attrname!r} property." + ) + raise TypeError(msg) from None + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + with self.lock: + # check if another thread filled cache while we awaited lock + val = cache.get(self.attrname, _NOT_FOUND) + if val is _NOT_FOUND: + val = self.func(instance) + try: + cache[self.attrname] = val + except TypeError: + msg = ( + f"The '__dict__' attribute on {type(instance).__name__!r} instance " + f"does not support item assignment for caching {self.attrname!r} property." + ) + raise TypeError(msg) from None + return val |