Similar to this question, I want to replace a property. Unlike that question, I do not want to override it in a sub-class. I want to replace it in the init and in the property itself for efficiency, so that it doesn't have to call a function which calculates the value each time the property is called.
I have a class which has a property on it. The constructor may take the value of the property. If it is passed the value, I want to replace the property with the value (not just set the property). This is because the property itself calculates the value, which is an expensive operation. Similarly, I want to replace the property with the value calculated by the property once it has been calculated, so that future calls to the property do not have to re-calculate:
class MyClass(object):
def __init__(self, someVar=None):
if someVar is not None: self.someVar = someVar
@property
def someVar(self):
self.someVar = calc_some_var()
return self.someVar
The above code does not work because doing self.someVar = does not replace the someVar function. It tries to call the property's setter, which is not defined.
I know I can achieve the same thing in a slightly different way as follows:
class MyClass(object):
def __init__(self, someVar=None):
self._someVar = someVar
@property
def someVar(self):
if self._someVar is None:
self._someVar = calc_some_var()
return self._someVar
This will be marginally less efficient as it will have to check for None every time the property is called. The application is performance critical, so this may or may not be good enough.
Is there a way to replace a property on an instance of a class? How much more efficient would it be if I was able to do this (i.e. avoiding a None check and a function call)?
What you are looking for is Denis Otkidach's excellent CachedAttribute:
class CachedAttribute(object):
'''Computes attribute value and caches it in the instance.
From the Python Cookbook (Denis Otkidach)
This decorator allows you to create a property which can be computed once and
accessed many times. Sort of like memoization.
'''
def __init__(self, method, name=None):
# record the unbound-method and the name
self.method = method
self.name = name or method.__name__
self.__doc__ = method.__doc__
def __get__(self, inst, cls):
# self: <__main__.cache object at 0xb781340c>
# inst: <__main__.Foo object at 0xb781348c>
# cls: <class '__main__.Foo'>
if inst is None:
# instance attribute accessed on class, return self
# You get here if you write `Foo.bar`
return self
# compute, cache and return the instance's attribute value
result = self.method(inst)
# setattr redefines the instance's attribute so this doesn't get called again
setattr(inst, self.name, result)
return result
It can be used like this:
def demo_cache():
class Foo(object):
@CachedAttribute
def bar(self):
print 'Calculating self.bar'
return 42
foo=Foo()
print(foo.bar)
# Calculating self.bar
# 42
Notice that accessing foo.bar
subsequent times does not call the getter function. (Calculating self.bar
is not printed.)
print(foo.bar)
# 42
foo.bar=1
print(foo.bar)
# 1
Deleting foo.bar
from foo.__dict__
re-exposes the property defined in Foo
.
Thus, calling foo.bar
again recalculates the value again.
del foo.bar
print(foo.bar)
# Calculating self.bar
# 42
demo_cache()
The decorator was published in the Python Cookbook and can also be found on ActiveState.
This is efficient because although the property exists in the class's __dict__
, after computation, an attribute of the same name is created in the instance's __dict__
. Python's attribute lookup rules gives precedence to the attribute in the instance's __dict__
, so the property in class becomes effectively overridden.
Sure, you can set the attribute in the private dictionary of the class instance, which takes precedence before calling the property function foo
(which is in the static dictionary A.__dict__
)
class A:
def __init__(self):
self._foo = 5
self.__dict__['foo'] = 10
@property
def foo(self):
return self._foo
assert A().foo == 10
If you want to reset again to work on the property, just del self.__dict__['foo']
class MaskingProperty():
def __init__(self, fget=None, name=None, doc=None):
self.fget = fget
if fget is not None:
self.name = fget.__name__
self.__doc__ = doc or fget.__doc__
def __call__(self, func):
self.fget = func
self.name = func.__name__
if not self.__doc__:
self.__doc__ = func.__doc__
return self
def __get__(self, instance, cls):
if instance is None:
return self
if self.fget is None:
raise AttributeError("seriously confused attribute <%s.%s>" % (cls, self.name))
result = self.fget(instance)
setattr(instance, self.name, result)
return result
This is basically the same as Denis Otkidach's CachedAttribute
, but slightly more robust in that it allows either:
@MaskingProperty
def spam(self):
...
or
@MaskingProperty() # notice the parens! ;)
def spam(self):
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With