Reputation: 12687
Is it possible to write a decorator that creates many properties at once?
Like instead of writing
class Test:
@property
def a(self):
return self.ref.a
@property
def b(self):
return self.ref.b
I'd like to write
class Test:
@properties("a", "b")
def prop(self, name):
return getattr(self.ref, name)
Is it possible? Do you recommend it?
Upvotes: 5
Views: 233
Reputation: 91017
This is not possible from within the class. However, you can modify the class afterwards. 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 the 2nd approach, you should 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
Upvotes: 0
Reputation: 110156
One provision in the lagnuage for what you probably really intend is writting the __setattr__
method for a class.
This method is caleed whenever an attribute that normally does not exist is is acessed on the instance:
>>> class Test(object):
... a = 0
... def __getattr__(self, attr):
... return attr
...
>>> t = Test()
>>> t.a
0
>>> t.b
'b'
>>> t.c
'c'
What you are directly asking is also possible, bute requires some hacks - that although not advisable by common sense, are widely used in production in the wild.
Namely, for a property to exist in Python, it is a class attribute ound to a special type of object - one that has at least the __get__
method. (to learn more check about the "Descriptor Protocol" on Python docs).
Now, trying to create several properties at once, like the code you pasted by example, would require that the property names would be injected in the class name space from the called function. It _ is possible_, and even used in production in Python, and not evenhard to achieve. But not pretty, nonetheless.
So, a possible way of avoiding this is to have a call that returns a sequence of "property" objects - that is clean, readable and maintanable:
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, *"a b c".split())
Upvotes: 2
Reputation: 157314
Recall that a decorator
@decorator(dec_args)
def foo(args):
pass
is just syntactic sugar for writing
def foo(args):
pass
foo = decorator(dec_args)(foo)
So it is not possible for a method decorator to result in more than one method (or property, etc.) to be added to a class.
An alternative might be a class decorator that injects the 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'd usually be clearer to have each property present within the body of the class:
a = forward_property('ref', 'a')
b = forward_property('ref', 'b')
where forward_property
returns a property object as appropriate implementing the descriptor protocol. This is friendlier to documentation and other static analysis tools, as well as (usually) the reader.
Upvotes: 3
Reputation: 601391
The easiest way of writing 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 only called for attributes that are not found in the current instance, so you can also add further methods and attributes to Proxy
.
Upvotes: 2