descriptor object for an attribute?

Eric Mahurin eric.mahurin at gmail.com
Sat Apr 14 18:34:27 EDT 2007


On Apr 11, 3:07 pm, Bruno Desthuilliers
<bdesth.quelquech... at free.quelquepart.fr> wrote:
> Eric Mahurin a écrit :
>
> > Is there a standard way to get a descriptor object for an arbitrary
> > object attribute - independent of whether it uses the descriptor/
> > property protocol or not.  I want some kind of handle/reference/
> > pointer to an attribute.
>
> I'm not sure to understand what you want exactly. A concrete example
> would probably help. But from what I understood, you could just use a
> thin wrapper like:
>
> _marker = object()
> class Accessor(object):
>    def __init__(self, obj, name):
>      self._obj = obj
>      self._name = name
>    def __call__(self, new_val=_marker):
>      if new_val is _marker:
>        return getattr(self._obj, self._name)
>      else:
>        setattr(self._obj, self._name, new_val)
>
> class Foo(object):
>    def __init__(self, bar, baak=42):
>      self.bar = bar
>      self.baak = baak
>
> foo = Foo('allo')
> bar = Accessor(foo, 'bar')
>
> assert bar() == foo.bar == 'allo'
> bar(42)
> assert bar() == foo.bar == 42
>
> If that doesn't answer your question, please pardon my stupidity and
> provide some more concrete example.

Thanks Bruno,

This does answer my question.  The protocol you implemented looks to
match a weakref except you can also set the value by giving an
argument to __call__ (an of course it implements a "hard" ref).  I was
considering this one already, but decided to use an attribute/
descriptor for getting/setting for a little better efficiency (no if
statement).  I also made my reference classes forward/proxy attributes
from the underlying object.  I used the attribute "_" for getting/
setting the underlying object to reduce the chances of colliding with
attributes of the underlying object.

I still don't like the resulting syntax, but it is workable.  I would
have to be able to do one of these:

my_ref()          # get underlying object - possible with __call__
my_ref() = obj # set underlying object - can't: no calling assign

my_ref[]          # get underlying object - can't: __getitem__
requires a key/index
my_ref[] = obj # set underlying object - can't: __setitem__ requires a
key/index

Below is a stripped down version of what I'm using now.  I'm showing
the references (sub-classed to links) being used in a singly linked
list (I'm really using them with a directed graph where children are
implemented with a singly linked list).  When implementing these types
of structures, the concept of a "reference" (to an object reference)
can be quite handy.  With a list/dict, you already have a handle to
values in a list/dict - an index/key.  This isn't quite a reference,
but usually it is good enough.  For other structures, a reference
becomes much more useful since you don't have this.

class Ref(object) :
    '''
    Anonymous reference to an object.
    '''
    def __init__(self, object=None) :
        '''
        >>> r = Ref(-1)
        >>> r._
        -1
        >>> r._ = 2
        >>> r._
        2
        '''
        self._ = object

    def __getattr__(self, name) :
        '''
        >>> r = Ref(-1)
        >>> r.__abs__()
        1
        '''
        return getattr(self._, name)


class LookupRef(Ref) :
    '''
    Reference to a value in a lookup (dict, list, tuple, etc).
    '''
    def __init__(self, lookup, key) :
        self._lookup = lookup
        self._key = key

    _ = property(
        lambda self: self._lookup.__getitem__(self._key),
        lambda self, value: self._lookup.__setitem__(self._key,
value),
        doc='''
        >>> r = LookupRef(('a','b','c'),1)
        >>> r._
        'b'
        >>> v = 0
        >>> r = LookupRef(locals(),'v')
        >>> r._ = 1
        >>> v
        1
        ''')


class AttrRef(Ref) :
    '''
    Reference to an object attribute.
    '''
    def __init__(self,obj,attr) :
        self._obj = obj
        self._attr = attr

    _ = property(
        lambda self: getattr(self._obj, self._attr),
        lambda self, value: setattr(self._obj, self._attr, value),
        doc='''
        >>> class Foo : bar = 123
        >>> f = Foo()
        >>> r = AttrRef(f,'bar')
        >>> r._
        123
        >>> r._ = 'abc'
        >>> f.bar
        'abc'
        ''')


class Node(object) :
    '''
    Node (including head) in a singly linked-list.
    '''
    def __init__(self, data=None, next=None) :
        self.data = data
        self.next = next

    def __add__(self, node) :
        '''
        >>> t = Node('a')+Node('b')
        >>> t.data
        'b'
        >>> t.next.data
        'a'
        '''
        node.next = self
        return node

    def __iter__(self) :
        '''
        An iterator through self and all other nodes linked.

        >>> [node.data for node in Node(1) + Node(2) + Node(3)]
        [3, 2, 1]
        '''
        node = self
        while node :
            yield node
            node = node.next

    def __repr__(self) :
        '''
        Gives the coordinates and the children.

        >>> Node(1)+Node(2)
        Node(1) + Node(2)
        '''
        return ' + '.join(reversed(
            ['Node('+repr(node.data)+')' for node in self]
        ))

    def push_next(self, node) :
        '''
        >>> l = Node(1)+Node(2)
        >>> l.push_next(Node(3))

        '''
        node.next = self.next
        self.next = node

    def pop_next(self) :
        '''
        >>> l = Node(1)+Node(2)+Node(3)
        >>> l.pop_next()
        Node(2)
        >>> l
        Node(1) + Node(3)
        '''
        node = self.next
        self.next = node.next
        node.next = None
        return node


class Link(Ref) :
    '''
    A link (type of reference) to a Node.
    '''
    def pop(self) :
        '''
        Pop off the current linked node (returning it) and link to the
        next node.

        >>> l = Link(Node(1)+Node(2)+Node(3))
        >>> l.pop()
        Node(3)
        >>> l._
        Node(1) + Node(2)
        '''
        node = self._
        self._ = node.next
        node.next = None
        return node

    def push(self, node) :
        '''
        Link to a new node and make the previous linked node the next
one.

        >>> l = Link(Node(1)+Node(2))
        >>> l.push(Node(3))
        >>> l._
        Node(1) + Node(2) + Node(3)
        '''
        node.next = self._
        self._ = node

    def __iter__(self) :
        '''
        Iterator to self and next links.

        >>> l = Link(Node(1)+Node(2)+Node(3)+Node(4)+Node(5))
        >>> for link in l :
        ...     # remove first node with even data
        ...     if not link.data%2 :
        ...         node = link.pop()
        ...         break
        >>> l._
        Node(1) + Node(2) + Node(3) + Node(5)
        >>> node
        Node(4)
        '''
        ref = self
        while ref._ :
            yield ref
            ref = AttrLink(ref._,'next')

class AttrLink(AttrRef, Link) : pass

class LookupLink(LookupRef, Link) : pass






More information about the Python-list mailing list