Decorator for many properties in Python

Is it possible to write a decorator that creates several properties at once?

How instead of recording

class Test: @property def a(self): return self.ref.a @property def b(self): return self.ref.b 

I would like to write

 class Test: @properties("a", "b") def prop(self, name): return getattr(self.ref, name) 

Is it possible? Do you recommend it?

+4
source share
4 answers

Recall that the decorator

 @decorator(dec_args) def foo(args): pass 

is just syntactic sugar for writing

 def foo(args): pass foo = decorator(dec_args)(foo) 

Thus, it is not possible for a method decorator to add more than one method (or property, etc.) to a class.

An alternative would be a class decorator that introduces properties:

 def multi_property(prop, *names): def inner(cls): for name in names: setattr(cls, name, property(lambda self, name=name: prop(self, name))) return inner @multi_property(prop, 'a', 'b') class Test: pass 

However, it would usually be clearer that each property is present in the class body:

 a = forward_property('ref', 'a') b = forward_property('ref', 'b') 

where forward_property returns the property object accordingly, implementing the handle protocol. This is more convenient for documentation and other static analysis tools, as well as (usually) for the reader.

+3
source

The easiest way to write a proxy for another object is to implement __getattr__() :

 class Proxy(object): def __init__(self, ref1, ref2): self._ref1 = ref1 self._ref2 = ref2 def __getatrr__(self, name): if name in ["a", "b", "c"]: return getattr(self._ref1, name) if name in ["d", "e", "f"]: return getattr(self._ref2, name) 

Note that __getattr__() is called only for attributes that are not found in the current instance, so you can also add additional methods and attributes to Proxy .

+2
source

One of the provisions in the camp for what you are probably really going to use is calling the __setattr__ method for the class.

This method is executed when an attribute that usually does not exist is in the instance:

 >>> class Test(object): ... a = 0 ... def __getattr__(self, attr): ... return attr ... >>> t = Test() >>> ta 0 >>> tb 'b' >>> tc 'c' 

What you directly ask is also possible, but requires some hacks - which, although not recommended by common sense, is widely used in production in the wild. Namely, in order for the property to exist in Python, it is a class attribute for a special type of object that has at least the __get__ method. (to learn more about the "Descriptor Protocol" in Python docs).

Now, trying to create several properties at once, for example, the code inserted into the example will require that property names be entered into the class namespace from the called function. It is possible, and even used in production in Python, and not even difficult to achieve. But not really, nonetheless.

Thus, a possible way to avoid this is to make a call that returns a sequence of โ€œpropertyโ€ objects - clean, readable, and customizable:

 class MultiProperty(object): def __init__(self, getter, setter, name): self.getter = getter self.setter = setter self.name = name def __get__(self, instance, owner): return self.getter(instance, self.name) def __set__(self, instance, value): return self.setter(instance, self.name, value) def multi_property(mgetter, msetter, *args): props = [] for name in args: props.append(MultiProperty(mgetter, msetter, name)) return props class Test(object): def multi_getter(self, attr_name): # isf desired, isnert some logic here return getattr(self, "_" + attr_name) def multi_setter(self, attr_name, value): # insert some logic here return setattr(self, "_" + attr_name, value) a,b,c = multi_property(multi_getter, multi_setter, *"abc".split()) 
+2
source

This is not possible from within the class. However, you can change the class later. See here:

 def makeprop(meth, name): # make a property calling the given method. # It is not really a method, but it gets called with the "self" first... return property(lambda self: meth(self, name)) def propfor(cls, *names): def wrap(meth): for name in names: # Create a property for a given object, in this case self, prop = makeprop(meth, name) setattr(cls, name, prop) return meth # unchanged return wrap class O(object): # just a dummy for your ref a = 9 b = 12 c = 199 class C(object): ref = O() # Put the wanted properties into the class afterwards: @propfor(C, "a", "b", "c") def prop(self, name): return getattr(self.ref, name) # alternative approach with a class decorator: def propdeco(*names): meth = names[-1] names = names[:-1] def classdeco(cls): propfor(cls, *names)(meth) # not nice, but reuses code above return cls return classdeco @propdeco("a", "b", "c", lambda self, name: getattr(self.ref, name)) class D(object): ref = O() print C().a print C().b print C().c print D().a print D().b print D().c 

If you prefer a second approach, write propdeco as

 def propdeco(*names): meth = names[-1] names = names[:-1] def classdeco(cls): for name in names: # Create a property for a given object, in this case self, prop = makeprop(meth, name) setattr(cls, name, prop) return cls return classdeco 
0
source

All Articles