How do I invalidate @cached_property in django
Question:
I am currently using @cached_property
on a model class and I would like to delete it on save so that it can be repopulated on the next call. How do I do this?
Example:
class Amodel():
#...model_fields....
@cached_property
def db_connection(self):
#get some thing in the db and cache here
instance = Amodel.objects.get(id=1)
variable = instance.db_connection
Amodel.objects.select_for_update().filter(id=1).update(#some variable)
#invalidate instance.db_connection
#new_variable = instance.db_connection
Thanks
Answers:
Just del it as documentation says. It will lead to recalculation on next access.
class SomeClass(object):
@cached_property
def expensive_property(self):
return datetime.now()
obj = SomeClass()
print obj.expensive_property
print obj.expensive_property # outputs the same value as before
del obj.expensive_property
print obj.expensive_property # outputs new value
For Python 3 it’s the same use of del
. Below is an example of a try/except block.
try:
del obj.expensive_property
except AttributeError:
pass
Edited heavily due to ongoing development… Now supports multiple tags for a given cached_property.
I encountered a similar issue, wherein I had a set of related cached_property
objects which all needed simultaneous invalidation. I solved it in this manner:
-
Extend cached_property
to accept tag values and include a decorator classmethod:
def __init__(self, func, *tags):
self.func = func
self.tags = frozenset(tags)
@classmethod
def tag(cls *tags):
return lambda f: cls(f, *tags)
-
In my other objects, use my new cached_property.tag
decorator classmethod to define tagged cached_property
methods:
@cached_property.tag("foo_group")
def foo(self):
return "foo"
-
On my object that makes use of the new decorator, write a method to invalidate all cached_property
values with the named tag by walking the __dict__
of the instantiated object’s class. This prevents accidental invocation of all cached_property
methods:
def invalidate(self, tag):
for key, value in self.__class__.__dict__.items():
if isinstance(value, cached_property) and tag in value.tags:
self.__dict__.pop(key, None)
Now, to invalidate, I merely invoke myobject.invalidate("foo_group")
.
I created a Django model mixin that invalidates all @cached_property
properties on the model when model.refresh_from_db()
is called. You can also manually invalidate the cached properties with model.invalidate_cached_properties()
.
from django.utils.functional import cached_property
class InvalidateCachedPropertiesMixin():
def refresh_from_db(self, *args, **kwargs):
self.invalidate_cached_properties()
return super().refresh_from_db(*args, **kwargs)
def invalidate_cached_properties(self):
for key, value in self.__class__.__dict__.items():
if isinstance(value, cached_property):
self.__dict__.pop(key, None)
https://gitlab.com/snippets/1747035
Inspired by Thomas Baden‘s answer.
If you don’t want to use try
and except
, and also write fewer lines, you can use:
if ("expensive_property" in obj.__dict__):
del obj.expensive_property
Or:
if ("expensive_property" in obj.__dict__):
delattr(obj, "expensive_property")
It will delete the cached property and it will be calculated again the next time it’s accessed.
Update: Don’t use if (hasattr(obj, "expensive_property")):
! It will calculate the property if it’s not cached already and will always return True
!
I am currently using @cached_property
on a model class and I would like to delete it on save so that it can be repopulated on the next call. How do I do this?
Example:
class Amodel():
#...model_fields....
@cached_property
def db_connection(self):
#get some thing in the db and cache here
instance = Amodel.objects.get(id=1)
variable = instance.db_connection
Amodel.objects.select_for_update().filter(id=1).update(#some variable)
#invalidate instance.db_connection
#new_variable = instance.db_connection
Thanks
Just del it as documentation says. It will lead to recalculation on next access.
class SomeClass(object):
@cached_property
def expensive_property(self):
return datetime.now()
obj = SomeClass()
print obj.expensive_property
print obj.expensive_property # outputs the same value as before
del obj.expensive_property
print obj.expensive_property # outputs new value
For Python 3 it’s the same use of del
. Below is an example of a try/except block.
try:
del obj.expensive_property
except AttributeError:
pass
Edited heavily due to ongoing development… Now supports multiple tags for a given cached_property.
I encountered a similar issue, wherein I had a set of related cached_property
objects which all needed simultaneous invalidation. I solved it in this manner:
-
Extend
cached_property
to accept tag values and include a decorator classmethod:def __init__(self, func, *tags): self.func = func self.tags = frozenset(tags) @classmethod def tag(cls *tags): return lambda f: cls(f, *tags)
-
In my other objects, use my new
cached_property.tag
decorator classmethod to define taggedcached_property
methods:@cached_property.tag("foo_group") def foo(self): return "foo"
-
On my object that makes use of the new decorator, write a method to invalidate all
cached_property
values with the named tag by walking the__dict__
of the instantiated object’s class. This prevents accidental invocation of allcached_property
methods:def invalidate(self, tag): for key, value in self.__class__.__dict__.items(): if isinstance(value, cached_property) and tag in value.tags: self.__dict__.pop(key, None)
Now, to invalidate, I merely invoke myobject.invalidate("foo_group")
.
I created a Django model mixin that invalidates all @cached_property
properties on the model when model.refresh_from_db()
is called. You can also manually invalidate the cached properties with model.invalidate_cached_properties()
.
from django.utils.functional import cached_property
class InvalidateCachedPropertiesMixin():
def refresh_from_db(self, *args, **kwargs):
self.invalidate_cached_properties()
return super().refresh_from_db(*args, **kwargs)
def invalidate_cached_properties(self):
for key, value in self.__class__.__dict__.items():
if isinstance(value, cached_property):
self.__dict__.pop(key, None)
https://gitlab.com/snippets/1747035
Inspired by Thomas Baden‘s answer.
If you don’t want to use try
and except
, and also write fewer lines, you can use:
if ("expensive_property" in obj.__dict__):
del obj.expensive_property
Or:
if ("expensive_property" in obj.__dict__):
delattr(obj, "expensive_property")
It will delete the cached property and it will be calculated again the next time it’s accessed.
Update: Don’t use if (hasattr(obj, "expensive_property")):
! It will calculate the property if it’s not cached already and will always return True
!