summaryrefslogtreecommitdiffstats
path: root/Lib/functools.py
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2018-08-28 07:11:56 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2018-08-28 07:11:56 (GMT)
commitd658deac6060ee92b449a3bf424b460eafd99f3e (patch)
treeb20a3a288b17dd17fbd5e555f139b2c1df80bcb7 /Lib/functools.py
parent216b745eafa7cd4a683a8405dcfbd7f5567f504c (diff)
downloadcpython-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.py55
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