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')
Related
I'm new to metaclasses, so I'm sorry, If my question is somehow stupid. I need to make a metaclass, that takes particular methods of a class and turns them into property methods or setters. So, if I have
class TestClass():
def __init__(self, x: int):
self._x = x
def get_x(self):
print("this is property")
return self._x
def set_x(self, x: int):
print("this is setter")
self._x = x
I want it to work like this:
class TestClass():
def __init__(self, x: int):
self._x = x
#property
def x(self):
print("this is property")
return self._x
#x.setter
def x(self, x: int):
print("this is setter")
self._x = x
For now I've just realized how to make it for property:
class PropertyConvert(type):
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val)
if name.startswith('set_'):
# ???
else:
new_attr[name] = val
return type.__new__(cls, future_class_name, future_class_parents, new_attr)
But I can't figure out how to do it for setters.
I highly recommend docs about descriptors, they are really nice written with many similar examples explained.
But answering your question, to make a setter work you need to use the same property function but fill second arguments.
class property(fget=None, fset=None, fdel=None, doc=None)
So code could look like that:
class PropertyConvert(type):
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
fget, fset, property_name = None, None, None
for name, val in future_class_attr.items():
if not name.startswith("__"):
if name.startswith("get_"):
property_name = name[4:]
fget = val
if name.startswith("set_"):
property_name = name[4:]
fset = val
else:
new_attr[name] = val
if n:
new_attr[property_name] = property(fget, fset)
return type.__new__(cls, future_class_name, future_class_parents, new_attr)
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()
There is a decorator with inheritance. It works well:
class bar(object):
def __init__(self,val):
self.val = val
#staticmethod
def decor(func):
def increment(obj, x):
return func(obj, x) + obj.val
return increment
class foo(bar):
def __init__(self):
bar.__init__(self)
#bar.decor
def add(self, x):
return x
But I want to add a parameter in the class foo:
class foo(bar):
def __init__(self,B):
bar.__init__(self)
self.B = B
And I want to input B into the decorator as an parameters, I've tried a scratch:
class bar(object):
def __init__(self,val):
self.val = val
#staticmethod
def decor(B):
def wrap(func):
def increment(obj, x):
return func(obj, x) + obj.val + B
return increment
return wrap
class foo(bar):
def __init__(self,B):
bar.__init__(self)
self.B = B
#bar.decor(B)
def add(self, x):
return x
But it didn't work. What am I doing wrong?
class bar(object):
def __init__(self, val):
self.val = val
#staticmethod
def decor(func):
def increment(obj, x):
return func(obj, x) + obj.val + obj.B
return increment
class foo(bar):
def __init__(self,val,B):
bar.__init__(self,val)
self.B = B
#bar.decor
def add(self, x):
return x
aa = foo(4, 1.5)
a = aa.add(1)
print(a)
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"
I have two classes A and B, where B inherits from A and overrides a property. A is not under my control so I cannot change it.
The code looks as follows:
class A():
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
raise AttributeError("can't set attribute")
When I try to call B(1) I obviously get AttributeError: can't set attribute.
I would like to have a different behaviour when value is set from inside class methods
#value.setter
def value(self, value):
if set from inside class hierarchy:
pass
else:
raise AttributeError("can't set attribute")
The module inspect does not seem to give me enough information to do this, except checking against a list of known functions.
You can inspect the stack to determine who called, and whether that it's in the class hierarchy to decide whether or not to allow it:
import inspect
def who_called():
frame = inspect.stack()[2][0]
if 'self' not in frame.f_locals:
return None, None
cls = frame.f_locals['self'].__class__
method = frame.f_code.co_name
return cls, method
class A(object):
def __init__(self, value):
self._value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
# Assuming this existed it would also work
def change_value(self, value):
self.value = value
Class B now checking:
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
cls, method = who_called()
if cls in B.__mro__ and method in A.__dict__:
self._value = value
else:
raise AttributeError("can't set attribute")
Proof:
b = B('does not raise error')
b.change_value('does not raise error')
b.value = 'raises error'
You could use the code that made the call to determine whether the call came from inside the class. Only throw an exception if the call didn't start with self.value =.
import re
import traceback
class A(object):
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
call = traceback.extract_stack(limit=2)[0][3]
if re.match(r'self.value\s*=', call):
pass
else:
raise AttributeError("can't set attribute")
b = B(1) # OK
b.value = 3 # Exception
Of course, this breaks as soon as you start calling your variables self:
self = B(1) # OK
self.value = 3 # Meh, doesn't fail