Published: 21 July 2020, 18:00
If you’re going to store data in the descriptor, the reasonable question is “where”.
So, the best solution is to store data in the class itself. But how to name the attribute?
@cached_property
that we implemented above, relies on the passed function name and it is wrong:
class C:
@cached_property
def a(self):
print('called')
return 1
b = a
c = C()
# `a` is cached:
c.a
# called
# 1
c.a
# 1
# but `b` isn't:
c.b
# called
# 1
c.b
# called
# 1
PEP-487 introduced __set_name__
hook. It is called on descriptor assignment to a class attribute and accepts the class itself at the name of the attribute. Let’s use it and fix the implementation:
class cached_property:
def __init__(self, func):
self.func = func
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, cls):
if obj is None:
return self
# we've replaced `self.func.__name__` by `self.name` here
value = obj.__dict__[self.name] = self.func(obj)
return value