Repository navigation
bpo-21145: Add cached_property decorator in functools #6982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
1cda951
72259cd
26f3f43
567e819
f7876d0
354a975
f006f68
d6bed4b
b41380d
b504a20
977ece0
5bdeaab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -880,26 +880,29 @@ def __isabstractmethod__(self): | |
| class cached_property: | ||
| def __init__(self, func): | ||
| self.func = func | ||
| self.attrname = func.__name__ | ||
| self.__doc__ = func.__doc__ | ||
| self.lock = RLock() | ||
|
|
||
| def __set_name__(self, owner, name): | ||
| self.attrname = name | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might make sense to raise error in this case, since
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. I guess that also answers the above question; we will raise error in case |
||
|
|
||
| def __get__(self, instance, owner): | ||
| if instance is None: | ||
| return self | ||
| attrname = self.func.__name__ | ||
| try: | ||
| cache = instance.__dict__ | ||
| except AttributeError: # objects with __slots__ have no __dict__ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tweak the comment here along similar lines to the note in the docs |
||
| msg = ( | ||
| f"No '__dict__' attribute on {type(instance).__name__!r} " | ||
| f"instance to cache {attrname!r} property." | ||
| f"instance to cache {self.attrname!r} property." | ||
| ) | ||
| raise TypeError(msg) from None | ||
| val = cache.get(attrname, _NOT_FOUND) | ||
| 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(attrname, _NOT_FOUND) | ||
| val = cache.get(self.attrname, _NOT_FOUND) | ||
| if val is _NOT_FOUND: | ||
| val = cache[attrname] = self.func(instance) | ||
| val = cache[self.attrname] = self.func(instance) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the |
||
| return val | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2313,6 +2313,7 @@ def f(*args): | |
| with self.assertRaisesRegex(TypeError, msg): | ||
| f() | ||
|
|
||
|
|
||
| class CachedCostItem: | ||
| _cost = 1 | ||
|
|
||
|
|
@@ -2327,6 +2328,17 @@ def cost(self): | |
| return self._cost | ||
|
|
||
|
|
||
| class OptionallyCachedCostItem: | ||
| _cost = 1 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed? It seems to be always shadowed in |
||
|
|
||
| def get_cost(self): | ||
| """The cost of the item.""" | ||
| self._cost += 1 | ||
| return self._cost | ||
|
|
||
| cached_cost = py_functools.cached_property(get_cost) | ||
|
|
||
|
|
||
| class CachedCostItemWait: | ||
|
|
||
| def __init__(self, event): | ||
|
|
@@ -2359,6 +2371,13 @@ def test_cached(self): | |
| self.assertEqual(item.cost, 2) | ||
| self.assertEqual(item.cost, 2) # not 3 | ||
|
|
||
| def test_cached_attribute_name_differs_from_func_name(self): | ||
| item = OptionallyCachedCostItem() | ||
| self.assertEqual(item.get_cost(), 2) | ||
| self.assertEqual(item.cached_cost, 3) | ||
| self.assertEqual(item.get_cost(), 4) | ||
| self.assertEqual(item.cached_cost, 3) | ||
|
|
||
| def test_threaded(self): | ||
| go = threading.Event() | ||
| item = CachedCostItemWait(go) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks redundant since
attrnameis set in__set_name__()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you suggest to do instead? This is the most reasonable fallback in case
__set_name__is not called in some unusual use of this class, and it avoids the object ever being in an incompletely initialized state.