In a class, I want to define N persistent properties. I can implement them as follow:
#property
def prop1(self):
return self.__prop1
#prop1.setter
def prop1(self, value):
self.__prop1 = value
persistenceManagement()
#property
def prop2(self):
return self.__prop2
#prop2.setter
def prop2(self, value):
self.__prop2 = value
persistenceManagement()
[...]
#property
def propN(self):
return self.__propN
#propN.setter
def propN(self, value):
self.__propN = value
persistenceManagement()
Of course, the only different thing between these blocks is the property name (prop1, prop2, ..., propN). persistenceManagement() is a function that has to be called when the value of one of these property changes.
Since these blocks of code are identical except for a single information (i.e., the property name), I suppose there must be some way to replace each of these blocks by single lines declaring the existence of a persistent property with a given name. Something like
def someMagicalPatternFunction(...):
[...]
someMagicalPatternFunction("prop1")
someMagicalPatternFunction("prop2")
[...]
someMagicalPatternFunction("propN")
...or maybe some decorating trick that I cannot see at the moment. Is someone has an idea how this could be done?
Properties are just descriptor classes and you can create your own and use them:
class MyDescriptor(object):
def __init__(self, name, func):
self.func = func
self.attr_name = '__' + name
def __get__(self, instance, owner):
return getattr(self, self.attr_name)
def __set__(self, instance, value):
setattr(self, self.attr_name, value)
self.func(self.attr_name)
def postprocess(attr_name):
print 'postprocess called after setting', attr_name
class Example(object):
prop1 = MyDescriptor('prop1', postprocess)
prop2 = MyDescriptor('prop2', postprocess)
obj = Example()
obj.prop1 = 'answer' # prints 'postprocess called after setting __prop1'
obj.prop2 = 42 # prints 'postprocess called after setting __prop2'
Optionally you can make it a little easier to use with something like this:
def my_property(name, postprocess=postprocess):
return MyDescriptor(name, postprocess)
class Example(object):
prop1 = my_property('prop1')
prop2 = my_property('prop2')
If you like the decorator # syntax, you could do it this way (which also alleviates having to type the name of the property twice) -- however the dummy functions it requires seem a little weird...
def my_property(method):
name = method.__name__
return MyDescriptor(name, postprocess)
class Example(object):
#my_property
def prop1(self): pass
#my_property
def prop2(self): pass
The property class (yes it's a class) is just one possible implementation of the descriptor protocol (which is fully documented here: http://docs.python.org/2/howto/descriptor.html). Just write your own custom descriptor and you'll be done.
Related
I have a class and I want to reassign a certain method to an hidden one of the same class.
In my case I have a generic filter that I want to assign to a specific implementation.
I'd like to have the func_map as a class attribute instead of an instance attribute because it seems more logic. How should I do that?
class MyClass:
func_map = {"eq" : __eq_filter}
def set_filter(self, func):
self.filter = MyClass.func_map[func]
def filter(self, value):
raise NotImplementedError
def __eq_filter(self, value):
return self.attribute == value
Maybe use the built-in #staticmethod decorator, which, according to the docs, allows to bound a method to the class instead of to the instance (Transform a method into a static method):
class MyClass:
func_map = {"eq" : __eq_filter}
def set_filter(self, func):
self.filter = func_map[func]
def filter(self, value):
raise NotImplementedError
#staticmethod
def __eq_filter(self, value):
return self.attribute == value
Check the docs for more info: https://docs.python.org/3/library/functions.html#staticmethod
I have a class as follows:
class A:
def __init__(self):
pass
def add_attr(self, name):
setattr(self, name, 'something')
How do I define custom setter, getter for self.name? I cannot use __setattr__, __getattribute__ because that will change the behaviour of add_attr too.
EDIT: the users of this class will add arbitrary number of attributes with arbitrary names:
a = A()
a.add_attr('attr1')
a.add_attr('attr2')
I want custom behavior for only these user added attributes.
Building off #Devesh Kumar Singh’s answer, I would implement it in some way like this:
class A:
def __init__(self):
self.attrs = {}
def __setattr__(self, key, value):
if key in self.attrs:
self.set_attr(key, value)
else:
object.__setattr__(self, key, value)
def __getattribute__(self, key):
if key in self.__dict__.get(attrs, {}):
return self.__dict__['get_attr'](self, key)
return object.__getattribute__(self, key)
def get_attr(self, key):
r = self.attrs[key]
# logic
return r
def set_attr(self, key, value):
# logic
self.attrs[key] = value
def add_attr(self, key, value=None):
self.attrs[key] = value
add_attr is only used to initialise the variable the first time. You could also edit __setattr__ to set all new attributes in the self.attrs rather than self.__dict__
Custom getter and setter logic? That's what a property is made for. Usually these are used to magically mask function calls and make them look like attribute access
class MyDoubler(object):
def __init__(self, x):
self._x = x
#property
def x(self):
return x * 2
#x.setter
def x(self, value):
self._x = value
>>> md = MyDoubler(10)
>>> md.x
20
>>> md.x = 20
>>> md.x
40
>>> md._x
20
But there's no rule saying you can't abuse that power to add custom behavior to your getters and setters.
class A(object):
def __init__(self):
pass
#staticmethod
def default_getter_factory(name):
def default_getter(self):
return self.name
return default_getter
#staticmethod
def default_setter_factory(name):
def default_setter(self, value):
setattr(self, name, value)
return default_setter
def add_attr(self, name, getterfactory=None, setterfactory=None):
private_name = f"_{name}"
if getterfactory is None:
getterfactory = self.__class__.default_getter_factory
if setterfactory is None:
setterfactory = self.__class__.default_setter_factory
getter, setter = getterfactory(private_name), setterfactory(private_name)
getter = property(getter)
setattr(self.__class__, name, getter)
setattr(self.__class__, name, getter.setter(setter))
That said this is all a bit silly, and chances are that whatever it is you're trying to do is a thing that shouldn't be done. Dynamic programming is all well and good, but if I were to review code that did this, I would think very long and hard about alternative solutions before approving it. This reeks of technical debt to me.
One possibility I could think of is to have a dictionary of dynamic attributes, and set and get the dynamic attributes using the dictionary
class A:
def __init__(self):
#Dictionary of attributes
self.attrs = {}
#Set attribute
def set_attr(self, name):
self.attrs[name] = 'something'
#Get attribute
def get_attr(self, name):
return self.attrs.get(name)
a = A()
a.set_attr('var')
print(a.get_attr('var'))
The output will be something
Or an alternate is to use property decorator to add arguments explicitly outside the class, as described here
class A:
def __init__(self):
pass
a = A()
#Add attributes via property decorator
a.attr_1 = property(lambda self: self.attr_1)
a.attr_2 = property(lambda self: self.attr_2)
#Assign them values and print them
a.attr_1 = 4
a.attr_2 = 6
print(a.attr_1, a.attr_2)
The output will be 4 6
I am gonna answer my own question just for reference. This is based on others' answers here. The idea is to use default __setattr__ and __getattribute__ on attributes not added through add_attr.
class A:
def __init__(self):
self.attrs = {}
def add_attr(self, name):
self.attrs[name] = 'something'
def __getattribute__(self, name):
try:
object.__getattribute__(self, 'attrs')[name] # valid only if added by user
# custom logic and return
except (KeyError, AttributeError):
return object.__getattribute__(self, name)
def __setattr__(self, name, val):
# similar to __getattribute__
I'm trying to add extra decorator for magic method (__get__) in descriptor class.
I'm able to do it when I use #property but not when I use descriptor class.
I check range because my object set registers on the bus and some registers can take only specific range of values:
import functools
def check_range(min, max):
def decorator(f):
#functools.wraps(f)
def wrap(self, value):
if value not in range(min, max+1):
return
return f(self, value)
return wrap
return decorator
This works:
class Foo:
def __init__(self):
self.device.init_smth('my_object')
#property
def my_object(self):
return self.device.get_value('my_object')
#my_object.setter
#check_range(0,1)
def my_object(self, value):
self.device.set_value('my_object', value)
a = Foo()
print(a.my_object)
a.my_object = 1
print(a.my_object)
a.myobject = -1
And in this example everything works the same but check_range is not invoked:
class Register:
def __init__(self, name, device):
self.name = name
device.init_smth(name)
def __get__(self, instance, owner):
return instance.device.get_value(self.name)
#check_range(0,1)
def __set__(self, instance, value):
instance.device.set_value(self.name, value)
class Foo:
def __init__(self):
self.my_object = Register('my_object', self.device)
a = Foo()
print(a.my_object)
a.my_object = 1
print(a.my_object)
a.myobject = -1
I may be wrong, but most probably your descriptor not invoked at all, decorator is not the problem. Descriptors meant to be used like
class Foo2:
my_object = Register('my_object', 'init_value')
— you're defining it like class attribute. And python will execute all machinery with __get__/__set__/__del__ if your class attribute supports it (i.e. it is descriptor).
This is why there is an "instance" argument in descriptor methods — you're defining descriptor as class variable, but i.e. __set__ method will receive actual instance of your class, so you can manage per-instance data, like your device
I was recently writing a definition for a pretty basic data class in Python and I came up with the following:
class A:
def __init__(self, **kwargs):
self.__a1 = kwargs.get('some_value', -1)
#property
def a1(self):
return self.__a1
#a1.setter
def a1(self, new_a1):
self.__a1 = new_a1
And it goes on. In this case, the value -1 could be replaced with a variety of "null" values: -1, "", [], etc., and some_value comes from an Enum I defined earlier.
Because the class definition contains several of these property definitions, and they're all very "same-y", I'd like to write a function to do this for me. I'm pretty sure it's possible in Python but I've never tried it so I was hoping for some pointers.
Assuming you want to simplify the repetitive property definitions, you can use a generic descriptor to simplify this significantly:
class ProtectedAttribute(object):
"""Basic descriptor functionality for a protected attribute.
Args:
name (str): The name of the attribute to back the descriptor
(usually the name the descriptor is assigned to with a single
additional leading underscore).
"""
def __init__(self, name, **kwargs):
self.name = name
def __get__(self, obj, typ):
return getattr(obj, self.name)
def __set__(self, obj, value):
setattr(obj, self.name, value)
def __delete__(self, obj):
delattr(obj, self.name)
Now you can just do:
class A(object):
a1 = ProtectedAttribute('__a1')
def __init__(self, **kwargs):
self.a1 = kwargs.get("some_value", -1)
Note also the use of dict.get to simplify __init__.
I want to define a class containing read and write methods, which can be called as follows:
instance.read
instance.write
instance.device.read
instance.device.write
To not use interlaced classes, my idea was to overwrite the __getattr__ and __setattr__ methods and to check, if the given name is device to redirect the return to self. But I encountered a problem giving infinite recursions. The example code is as follows:
class MyTest(object):
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
if name=="device":
print "device test"
else:
setattr(self, name, value)
test = MyTest(1)
As in __init__ the code tried to create a new attribute x, it calls __setattr__, which again calls __setattr__ and so on. How do I need to change this code, that, in this case, a new attribute x of self is created, holding the value 1?
Or is there any better way to handle calls like instance.device.read to be 'mapped' to instance.read?
As there are always questions about the why: I need to create abstractions of xmlrpc calls, for which very easy methods like myxmlrpc.instance,device.read and similar can be created. I need to 'mock' this up to mimic such multi-dot-method calls.
You must call the parent class __setattr__ method:
class MyTest(object):
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
if name=="device":
print "device test"
else:
super(MyTest, self).__setattr__(name, value)
# in python3+ you can omit the arguments to super:
#super().__setattr__(name, value)
Regarding the best-practice, since you plan to use this via xml-rpc I think this is probably better done inside the _dispatch method.
A quick and dirty way is to simply do:
class My(object):
def __init__(self):
self.device = self
Or you can modify self.__dict__ from inside __setattr__():
class SomeClass(object):
def __setattr__(self, name, value):
print(name, value)
self.__dict__[name] = value
def __init__(self, attr1, attr2):
self.attr1 = attr1
self.attr2 = attr2
sc = SomeClass(attr1=1, attr2=2)
sc.attr1 = 3
You can also use object.
class TestClass:
def __init__(self):
self.data = 'data'
def __setattr__(self, name, value):
print("Attempt to edit the attribute %s" %(name))
object.__setattr__(self, name, value)
or you can just use #property:
class MyTest(object):
def __init__(self, x):
self.x = x
#property
def device(self):
return self
If you don't want to specify which attributes can or cannot be set, you can split the class to delay the get/set hooks until after initialization:
class MyTest(object):
def __init__(self, x):
self.x = x
self.__class__ = _MyTestWithHooks
class _MyTestWithHooks(MyTest):
def __setattr__(self, name, value):
...
def __getattr__(self, name):
...
if __name__ == '__main__':
a = MyTest(12)
...
As noted in the code you'll want to instantiate MyTest, since instantiating _MyTestWithHooks will result in the same infinite recursion problem as before.