I have a class A with three attributes a,b,c, where a is calculated from b and c (but this is expensive). Moreover, attributes b and c are likely to change over times. I want to make sure that:
a is cached once it is calculated and then reproduced from cache
if b or c change then the next time a is needed it must be recomputed to reflect the change
the following code seems to work:
class A():
def __init__(self, b, c):
self._a = None
self._b = b
self._c = c
#property
def a(self):
if is None:
self.update_a()
return self._a
def update_a(self):
"""
compute a from b and c
"""
print('this is expensive')
self._a = self.b + 2*self.c
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value
self._a = None #make sure a is recalculated before its next use
#property
def c(self):
return self._c
#c.setter
def c(self, value):
self._c = value
self._a = None #make sure a is recalculated before its next use
however this approach does not seem very good for many reasons:
the setters of b and c needs to know about a
it becomes a mess to write and maintain if the dependency-tree grows larger
it might not be apparent in the code of update_a what its dependencies are
it leads to a lot of code duplication
Is there an abstract way to achieve this that does not require me to do all the bookkeeping myself?
Ideally, I would like to have some sort of decorator which tells the property what its dependencies are so that all the bookkeeping happens under the hood.
I would like to write:
#cached_property_depends_on('b', 'c')
def a(self):
return self.b+2*self.c
or something like that.
EDIT: I would prefer solutions that do not require that the values assigned to a,b,c be immutable. I am mostly interested in np.arrays and lists but I would like the code to be reusable in many different situations without having to worry about mutability issues.
You could use functools.lru_cache:
from functools import lru_cache
from operator import attrgetter
def cached_property_depends_on(*args):
attrs = attrgetter(*args)
def decorator(func):
_cache = lru_cache(maxsize=None)(lambda self, _: func(self))
def _with_tracked(self):
return _cache(self, attrs(self))
return property(_with_tracked, doc=func.__doc__)
return decorator
The idea is to retrieve the values of tracked attributes each time the property is accessed, pass them to the memoizing callable, but ignore them during the actual call.
Given a minimal implementation of the class:
class A:
def __init__(self, b, c):
self._b = b
self._c = c
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value
#property
def c(self):
return self._c
#c.setter
def c(self, value):
self._c = value
#cached_property_depends_on('b', 'c')
def a(self):
print('Recomputing a')
return self.b + 2 * self.c
a = A(1, 1)
print(a.a)
print(a.a)
a.b = 3
print(a.a)
print(a.a)
a.c = 4
print(a.a)
print(a.a)
outputs
Recomputing a
3
3
Recomputing a
5
5
Recomputing a
11
11
Fortunately, a dependency management system like this is easy enough to implement - if you're familiar with descriptors and metaclasses.
Our implementation needs 4 things:
A new type of property that knows which other properties depend on it. When this property's value changes, it will notify all properties that depend on it that they have to re-calculate their value. We'll call this class DependencyProperty.
Another type of DependencyProperty that caches the value computed by its getter function. We'll call this DependentProperty.
A metaclass DependencyMeta that connects all the DependentProperties to the correct DependencyProperties.
A function decorator #cached_dependent_property that turns a getter function into a DependentProperty.
This is the implementation:
_sentinel = object()
class DependencyProperty(property):
"""
A property that invalidates its dependencies' values when its value changes
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.dependent_properties = set()
def __set__(self, instance, value):
# if the value stayed the same, do nothing
try:
if self.__get__(instance) is value:
return
except AttributeError:
pass
# set the new value
super().__set__(instance, value)
# invalidate all dependencies' values
for prop in self.dependent_properties:
prop.cached_value = _sentinel
#classmethod
def new_for_name(cls, name):
name = '_{}'.format(name)
def getter(instance, owner=None):
return getattr(instance, name)
def setter(instance, value):
setattr(instance, name, value)
return cls(getter, setter)
class DependentProperty(DependencyProperty):
"""
A property whose getter function depends on the values of other properties and
caches the value computed by the (expensive) getter function.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cached_value = _sentinel
def __get__(self, instance, owner=None):
if self.cached_value is _sentinel:
self.cached_value = super().__get__(instance, owner)
return self.cached_value
def cached_dependent_property(*dependencies):
"""
Method decorator that creates a DependentProperty
"""
def deco(func):
prop = DependentProperty(func)
# we'll temporarily store the names of the dependencies.
# The metaclass will fix this later.
prop.dependent_properties = dependencies
return prop
return deco
class DependencyMeta(type):
def __new__(mcls, *args, **kwargs):
cls = super().__new__(mcls, *args, **kwargs)
# first, find all dependencies. At this point, we only know their names.
dependency_map = {}
dependencies = set()
for attr_name, attr in vars(cls).items():
if isinstance(attr, DependencyProperty):
dependency_map[attr] = attr.dependent_properties
dependencies.update(attr.dependent_properties)
attr.dependent_properties = set()
# now convert all of them to DependencyProperties, if they aren't
for prop_name in dependencies:
prop = getattr(cls, prop_name, None)
if not isinstance(prop, DependencyProperty):
if prop is None:
# it's not even a property, just a normal instance attribute
prop = DependencyProperty.new_for_name(prop_name)
else:
# it's a normal property
prop = DependencyProperty(prop.fget, prop.fset, prop.fdel)
setattr(cls, prop_name, prop)
# finally, inject the property objects into each other's dependent_properties attribute
for prop, dependency_names in dependency_map.items():
for dependency_name in dependency_names:
dependency = getattr(cls, dependency_name)
dependency.dependent_properties.add(prop)
return cls
And finally, some proof that it actually works:
class A(metaclass=DependencyMeta):
def __init__(self, b, c):
self.b = b
self.c = c
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value + 10
#cached_dependent_property('b', 'c')
def a(self):
print('doing expensive calculations')
return self.b + 2*self.c
obj = A(1, 4)
print('b = {}, c = {}'.format(obj.b, obj.c))
print('a =', obj.a)
print('a =', obj.a) # this shouldn't print "doing expensive calculations"
obj.b = 0
print('b = {}, c = {}'.format(obj.b, obj.c))
print('a =', obj.a) # this should print "doing expensive calculations"
Related
I have a class which has fields that would all be properties with pass through getters and setters that are validated in a certain way, such that it would satisfy the following pattern:
import numpy as np
import typing
def validate_field(value, dtype: typing.Type):
limits = np.iinfo(dtype)
assert limits.min < value < limits.max, \
"value shoule be in range: {} < {} < {}".format(limits.min, value,
limits.max)
return value
class Foo:
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
#property
def a(self):
return self._a
#property
def b(self):
return self._b
#property
def c(self):
return self._c
#a.setter
def a(self, value):
self._a = validate_field(value, self._a.dtype)
#b.setter
def b(self, value):
self._b = validate_field(value, self._b.dtype)
#c.setter
def c(self, value):
self._c = validate_field(value, self._c.dtype)
I want to eliminate having to type a separate property and setter decorator for each method.
I thought about using properties manually via
self._a = a
def set_a(self, value):
self._a = validate_field(value, self._a.dtype)
self.a = property(lambda self: self._a, set_a)
...
However, it seemed I would still have to manually define a function that accessed the required member for both setter and getter, so I wasn't really saving much work.
If there was a way to automatically generate such functions via naming the parameter e.g.:
def generate_function(self, parameter)
def temp(self, value):
self.parameter = validate_field(value, self.parameter.dtype)
return temp
then I wouldn't have any issues, but right now I don't see how to accomplish this.
Is there a way for me to generate these functions with a single decorator per field or automated function based property generation in __init__?
You can use getattr() and setattr(), or direct dictionary access via self.__dict__, to parametrize the attribute name:
def validated_property(name):
def getter(self):
return getattr(self, name)
def setter(self, value):
dtype = getter(self).dtype
setattr(self, name, validate_field(value, dtype))
return property(getter, setter)
then use this as
class Foo:
# ...
a = validated_property('_a')
b = validated_property('_b')
c = validated_property('_c')
etc.
If you are using Python 3.6 or newer, you can avoid having to repeat the attribute name and generate one from the name for the property (by prefixing it with _, for example), by implementing your own descriptor object, which is passed the name under which it is being assigned to a class via the descriptor.__set_name__() method:
class ValidatedProperty:
_name = None
def __set_name__(self, owner, name):
self._name = '_' + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self._name)
def __set__(self, instance, value):
dtype = self.__get__(instance, type(instance)).dtype
setattr(instance, self._name, validate_field(value, dtype))
then use this like this:
class Foo:
# ...
a = ValidatedProperty()
b = ValidatedProperty()
c = ValidatedProperty()
How can I create multiple property decorators with self defined function as getter and setter based on following class structure? I have try to use
setattr(self, 'a', property(_to_get('a'), _to_set('a'))) but it does not work.
class ABC:
def __init__(self):
pass
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
#property
def a(self):
res = self._to_get('a')
return res.split(' ')[0]
#a.setter
def a(self, value)
self._to_set('a', value)
#property
def b(self):
res = self._to_get('b')
return res.split(' ')[1]
#b.setter
def b(self, value)
self._to_set('b', value)
#property
def c(self):
res = self._to_get('c')
return res.split(' ')[2]
#c.setter
def c(self, value)
self._to_set('c', value)
No reason why something like this wouldn't work:
class A(object):
def __init__(self):
self._a = None
#property
def a(self):
return self._a
#a.setter
def a(self, x):
self._a = x
#a.deleter
def a(self):
del self._a
#property
def b(self):
return self._b
#b.setter
def b(self, x):
self._b = x
#b.deleter
def b(self):
del self._b
#property
def c(self):
return self._c
#c.setter
def c(self, x):
self._c = x
#c.deleter
def c(self):
del self._c
Consider your original class written without decorator syntax. (The translation may not be 100% accurate, but should be close enough to illustrate the point I want to make.)
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(lambda self: ABC._to_get(self, 'a').split(' ')[0],
lambda self, value: ABC._to_set(self, 'a', value))
b = property(lambda self: ABC._to_get(self, 'b').split(' ')[1],
lambda self, value: ABC._to_set(self, 'b', value))
c = property(lambda self: ABC._to_get(self, 'c').split(' ')[2],
lambda self, value: ABC._to_set(self, 'c', value))
a, b and c are all basically the same thing, but parameterized
by the name of the property and an integer.
def make_getter(attr, x):
def getter(self):
return self._to_get(attr).split(' ')[x]
return getter
def make_setter(attr):
def setter(self, value):
self._to_set(attr, value)
return setter
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(make_getter('a', 0), make_setter('a'))
b = property(make_getter('b', 1), make_setter('b'))
c = property(make_getter('c', 2), make_setter('c'))
Something like the following should also work (not heavily tested), moving the logic into a subclass of property.
class Foo(property):
def __init__(self, x):
super().__init__(self._to_get, self._to_set)
self.x = x
# name is the class attribute the instance of Foo
# will be assigned to
def __set_name__(self, owner, name):
self.attr = name
# In both of the following, obj is the instance that actually
# invokes the parameter. You would probably want to pass it
# to something_function and do_something as well.
def _to_get(self, obj):
return something_function(self.attr).split(' ')[self.x]
def _to_set(self, obj, value):
do_something(self.attr, value)
class ABC:
a = Foo(0) # Will call a.__set_name__(ABC, 'a')
b = Foo(1) # Will call b.__set_name__(ABC, 'b')
c = Foo(2) # Will call c.__set_name__(ABC, 'c')
Where I am now looks like this:
class A(object):
def __init__(self, val):
self.x=val
self.y=42
# other fields
class B(object):
def __init__(self):
self.a=22
# other fields
class C(A,B):
def __init__(self, val):
super(C,self).__init__(val)
#property
def x(self):
# if A.x is None return a value that I can compute from A.y and B.a
# if A.x is not None return it
#x.setter
def x(self, val):
# set the field value
Sometimes I just want to set an assumed value for x by hand, in which case I would just use an A. In other cases I want to use a more complicated approach that involves computing A.x's value on the basis of information that is organized into a B. The idea in this code is to make a C class that can look like an A (in terms of the x field) but doesn't need that field value to be set by hand, instead it just gets derived.
What I can't figure out is how to have the C.x property shadow the A.x field in a sensible way.
The line self.x = val in the A.__init__ method will simply invoke your C.x setter. You already have everything handled here. You are handling per instance attributes here, not attributes on a class that are inherited by subclasses.
All you need to do is to set a different attribute in the setter to represent the x value. You could name it _x, for example:
class C(A, B):
_x = None
#property
def x(self):
if self._x is not None:
return self._x
return self.a + self.y
#x.setter
def x(self, val):
self._x = val
Note that if all C.__init__ does is call super().__init__, you don't need it at all. However, you do need to make sure at least A.__init__() plays along in the inheritance structure; add in more calls to super().__init__():
class A(object):
def __init__(self, val, *args, **kwargs):
super(A, self).__init__(*args, **kwargs)
self.x = val
self.y = 42
class B(object):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.a = 22
Using *args and **kwargs allows these methods to pass on any extra arguments to other classes in the hierarchy.
Demo, using the above classes:
>>> c = C(None)
>>> c.x
64
>>> c.x = 15
>>> c.x
15
In my code, I have a data store with multiple variables set to instances of a class similar to that below. (The reason is that this Interval class has lots of operator overriding functions).
class Interval(object):
def __init__(self, value):
self.value = value
data_store.a = Interval(1)
I want any references to data_store.a to return self.value rather than the Interval instance. Is this possible?
As an alternative to Malik's answer, you could make a a #property, the Pythonic equivalent of get and set for managing access to internal attributes:
class DataStore(object):
def __init__(self):
self.a = Interval(1)
#property
def a(self):
return self._a.value
#a.setter
def a(self, value):
self._a = value
Here _a is a private-by-convention attribute that stores the Interval instance. This works as you want it:
>>> store = DataStore()
>>> store.a
1
You need to extend your data store whose attribute is an interval object:
class DataStore(object):
def __init__(self):
self.a = Interval(1)
def __getattribute__(self, attr):
if attr == 'a':
return object.__getattribute__(self, 'a').value
if attr != 'a':
return object.__getattribute__(self, attr)
I am trying to override the __setattr__ method of a Python class, since I want to call another function each time an instance attribute changes its value. However, I don't want this behaviour in the __init__ method, because during this initialization I set some attributes which are going to be used later:
So far I have this solution, without overriding __setattr__ at runtime:
class Foo(object):
def __init__(self, a, host):
object.__setattr__(self, 'a', a)
object.__setattr__(self, 'b', b)
result = self.process(a)
for key, value in result.items():
object.__setattr__(self, key, value)
def __setattr__(self, name, value):
print(self.b) # Call to a function using self.b
object.__setattr__(self, name, value)
However, I would like to avoid these object.__setattr__(...) and override __setattr__ at the end of the __init__ method:
class Foo(object):
def __init__(self, a, b):
self.a = a
self.b = b
result = self.process(a)
for key, value in result.items():
setattr(self, key, value)
# override self.__setattr__ here
def aux(self, name, value):
print(self.b)
object.__setattr__(self, name, value)
I have tried with self.__dict__['__setitem__'] = self.aux and object.__setitem__['__setitem__'] = self.aux, but none of these attemps has effect. I have read this section of the data model reference, but it looks like the assignment of the own __setattr__ is a bit tricky.
How could be possible to override __setattr__ at the end of __init__, or at least have a pythonic solution where __setattr__ is called in the normal way only in the constructor?
Unfortunately, there's no way to "override, after init" python special methods; as a side effect of how that lookup works. The crux of the problem is that python doesn't actually look at the instance; except to get its class; before it starts looking up the special method; so there's no way to get the object's state to affect which method is looked up.
If you don't like the special behavior in __init__, you could refactor your code to put the special knowledge in __setattr__ instead. Something like:
class Foo(object):
__initialized = False
def __init__(self, a, b):
try:
self.a = a
self.b = b
# ...
finally:
self.__initialized = True
def __setattr__(self, attr, value):
if self.__initialzed:
print(self.b)
super(Foo, self).__setattr__(attr, value)
Edit: Actually, there is a way to change which special method is looked up, so long as you change its class after it has been initialized. This approach will send you far into the weeds of metaclasses, so without further explanation, here's how that looks:
class AssignableSetattr(type):
def __new__(mcls, name, bases, attrs):
def __setattr__(self, attr, value):
object.__setattr__(self, attr, value)
init_attrs = dict(attrs)
init_attrs['__setattr__'] = __setattr__
init_cls = super(AssignableSetattr, mcls).__new__(mcls, name, bases, init_attrs)
real_cls = super(AssignableSetattr, mcls).__new__(mcls, name, (init_cls,), attrs)
init_cls.__real_cls = real_cls
return init_cls
def __call__(cls, *args, **kwargs):
self = super(AssignableSetattr, cls).__call__(*args, **kwargs)
print "Created", self
real_cls = cls.__real_cls
self.__class__ = real_cls
return self
class Foo(object):
__metaclass__ = AssignableSetattr
def __init__(self, a, b):
self.a = a
self.b = b
for key, value in process(a).items():
setattr(self, key, value)
def __setattr__(self, attr, value):
frob(self.b)
super(Foo, self).__setattr__(attr, value)
def process(a):
print "processing"
return {'c': 3 * a}
def frob(x):
print "frobbing", x
myfoo = Foo(1, 2)
myfoo.d = myfoo.c + 1
#SingleNegationElimination's answer is great, but it cannot work with inheritence, since the child class's __mro__ store's the original class of super class. Inspired by his answer, with little change,
The idea is simple, switch __setattr__ before __init__, and restore it back after __init__ completed.
class CleanSetAttrMeta(type):
def __call__(cls, *args, **kwargs):
real_setattr = cls.__setattr__
cls.__setattr__ = object.__setattr__
self = super(CleanSetAttrMeta, cls).__call__(*args, **kwargs)
cls.__setattr__ = real_setattr
return self
class Foo(object):
__metaclass__ = CleanSetAttrMeta
def __init__(self):
super(Foo, self).__init__()
self.a = 1
self.b = 2
def __setattr__(self, key, value):
print 'after __init__', self.b
super(Foo, self).__setattr__(key, value)
class Bar(Foo):
def __init__(self):
super(Bar, self).__init__()
self.c = 3
>>> f = Foo()
>>> f.a = 10
after __init__ 2
>>>
>>> b = Bar()
>>> b.c = 30
after __init__ 2