Reputation: 10163
Is there a way to make a Python @property
act as a setter and getter all at once?
I feel like I've seen this somewhere before but can't remember and can't recreate the solution myself.
For example, instead of:
class A(object):
def __init__(self, b): self.b = b
def get_c(self): return self.b.c
def set_c(self, value): self.b.c = value
c = property(get_c, set_c)
we could somehow signal that for A
objects, the c
attribute is really equivalent to b.c
for getter, setter (and deleter if we like).
This would be particularly useful when we need A
to be a proxy wrapper around B
objects (of which b
is an instance) but share only the data attributes and no methods. Properties such as these would allow the A
and B
objects' data to stay completely in sync while both are used by the same code.
Upvotes: 3
Views: 8180
Reputation: 90316
Here's another way of doing it, statically forwarding properties from one object to another, but with economy.
It allows to forward get/set property in two lines, and aread-only property in one line, making use of dynamic property definition at the class level and lambdas.
class A:
"""Classic definition of property, with decorator"""
_id = ""
_answer = 42
@property
def id(self):
return self._id
@id.setter
def id(self, value):
self._id = value
@property
def what(self):
return self._answer
class B:
obj = A()
# Forward "id" from self.obj
id = property(lambda self: self.obj.id,
lambda self, value: setattr(self.obj, "id", value))
# Forward read-only property from self.obj
what = property(lambda self: self.obj.what)
Upvotes: 0
Reputation: 1126
I think you are looking for this forwardTo class as posted on ActiveState.
This recipe lets you transparently forward attribute access to another object in your class. This way, you can expose functionality from some member of your class instance directly, e.g. foo.baz() instead of foo.bar.baz().
class forwardTo(object):
"""
A descriptor based recipe that makes it possible to write shorthands
that forward attribute access from one object onto another.
>>> class C(object):
... def __init__(self):
... class CC(object):
... def xx(self, extra):
... return 100 + extra
... foo = 42
... self.cc = CC()
...
... localcc = forwardTo('cc', 'xx')
... localfoo = forwardTo('cc', 'foo')
...
>>> print C().localcc(10)
110
>>> print C().localfoo
42
Arguments: objectName - name of the attribute containing the second object.
attrName - name of the attribute in the second object.
Returns: An object that will forward any calls as described above.
"""
def __init__(self, objectName, attrName):
self.objectName = objectName
self.attrName = attrName
def __get__(self, instance, owner=None):
return getattr(getattr(instance, self.objectName), self.attrName)
def __set__(self, instance, value):
setattr(getattr(instance, self.objectName), self.attrName, value)
def __delete__(self, instance):
delattr(getattr(instance, self.objectName), self.attrName)
For a more robust code, you may want to consider replacing getattr(instance, self.objectName)
with operator.attrgetter(self.objectName)(instance)
. This would allow objectName
to be a dotted name (e.g., so you could have A.c
be a proxy for A.x.y.z.d
).
Upvotes: 4
Reputation: 365717
If you're trying to delegate a whole slew of properties from any A object to its b
member, it's probably easier to do that inside __getattr__
, __setattr__
, and __delattr__
, e.g.:
class A(object):
delegated = ['c', 'd', 'e', 'f']
def __getattr__(self, attr):
if attr in A.delegated:
return getattr(self.b, attr)
raise AttributeError()
I haven't shown the __setattr__
and __delattr__
definitions here, for brevity, and to avoid having to explain the difference between __getattr__
and __getattribute__
. See the docs if you need more information.
This is readily extensible to classes that want to proxy different attributes to different members:
class A(object):
b_delegated = ['c', 'd', 'e', 'f']
x_delegated = ['y', 'z']
def __getattr__(self, attr):
if attr in A.b_delegated:
return getattr(self.b, attr)
elif attr in A.x_delegated:
return getattr(self.x, attr)
else:
raise AttributeError()
If you need to delegate all attributes, dynamically, that's almost as easy. You just get a list of self.b
's attributes (or self.b.__class__
's) at init time or at call time (which of the four possibilities depends on exactly what you want to do), and use that in place of the static list b_delegated
.
You can of course filter this by name (e.g., to remove _private
methods), or by type, or any arbitrary predicate (e.g., to remove any callable
attributes).
Or combine any of the above.
At any rate, this is the idiomatic way to do (especially dynamic) proxying in Python. It's not perfect, but trying to invent a different mechanism is probably not a good idea.
And in fact, it's not really meant to be perfect. This is something you shouldn't be doing too often, and shouldn't be trying to disguise when you do it. It's obvious that a ctypes.cdll
or a pyobjc
module is actually delegating to something else, because it's actually useful for the user to know that. If you really need to delegate most of the public interface of one class to another, and don't want the user to know about the delegation… maybe you don't need it. Maybe it's better to just expose the private object directly, or reorganize your object model so the user is interacting with the right things in the first place.
Upvotes: 4
Reputation: 26160
There's the decorator syntax for creating properties, then there are full blown custom-defined descriptors, but since the setter/getter pseudo-private pattern is actively discouraged in Python and the Python community, there isn't really a widely distributed or commonly used way to do what you are looking for.
For proxy objects, you can use __getattr__
, __setattr__
, and __getattribute__
, or try to manipulate things earlier in the process by fooling around with __new__
or a metaclass
.
Upvotes: 3
Reputation: 10504
def make_property(parent, attr):
def get(self):
return getattr(getattr(self, parent), attr)
def set(self, value):
setattr(getattr(self, parent), attr, value)
return property(get, set)
class A(object):
def __init__(self, b): self.b = b
c = make_property('b', 'c')
Upvotes: 1