bossylobster
bossylobster

Reputation: 10163

Attribute mapping with a Python property

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).

Motivation:

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

Answers (5)

CharlesB
CharlesB

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

bfroehle
bfroehle

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

abarnert
abarnert

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

Silas Ray
Silas Ray

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

proppy
proppy

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

Related Questions