Replace property to increase performance

Situation

Like this question , I want to replace a property. Unlike this question, I do not want to redefine it in a subclass. I want to replace it in init and in the property itself for efficiency, so that it does not need to call a function that evaluates the value each time the property is called.

I have a class that has a property on it. The constructor can take a property value. If a value is passed to it, I want to replace the property with a value (and not just set the property). This is because the property itself calculates a value, which is an expensive operation. Similarly, I want to replace a property with the value computed by the property as soon as it has been calculated, so that future calls to the property do not need to be re-read:

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 

Problem

The above code does not work because self.someVar = does not replace the someVar function. It tries to call the setter property, which is undefined.

Potential solution

I know that I can achieve the same a little bit 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 each time the property is called, it will check None. The app has critical performance, so it may or may not be good enough.

Question

Is there a way to replace a property with an instance of the class? How much more efficient would it be if I could do this (i.e. Avoid checking None and calling a function)?

+4
source share
3 answers

What are you looking for, Denis Otkidach 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 attribute value result = self.method(inst) # setattr redefines the instance attribute so this doesn't get called again setattr(inst, self.name, result) return result 

It can be used as follows:

 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 

Please note that access to foo.bar later times does not call the getter function. ( Calculating self.bar not printed.)

  print(foo.bar) # 42 foo.bar=1 print(foo.bar) # 1 

Removing foo.bar from foo.__dict__ overrides the property defined in Foo . Thus, calling foo.bar again computes the value.

  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 effective because although the property exists in the __dict__ class, after evaluating the __dict__ instance, an attribute with the same name is created. Python attribute search rules give priority to an attribute in an __dict__ instance, so the property in the class becomes actually overridden.

+14
source

Of course, you can set the attribute in the closed dictionary of the class instance, which takes precedence over the call to the property foo function (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 with this property, just del self.__dict__['foo']

+1
source
 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 CachedAttribute , but a bit more robust as it allows either:

 @MaskingProperty def spam(self): ... 

or

 @MaskingProperty() # notice the parens! ;) def spam(self): ... 
+1
source

All Articles