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