override a property
Alex Martelli
aleaxit at yahoo.com
Fri Oct 21 21:21:47 EDT 2005
Robin Becker <robin at reportlab.com> wrote:
...
> in answer to Bengt & Bruno here is what I'm sort of playing with. Alex
> suggests class change as an answer, but that looks really clunky to me.
> I'm not sure what
Changing class is indeed 'clunky', though it might have been necessary
depending on how one interpreted your original specs.
> Alex means by
>
> > A better design might be to use, instead of the builtin
> > type 'property', a different custom descriptor type that is specifically
> > designed for your purpose -- e.g., one with a method that instances can
> > call to add or remove themselves from the set of "instances overriding
> > this ``property''" and a weak-key dictionary (from the weakref module)
> > mapping such instances to get/set (or get/set/del, if you need to
> > specialize "attribute deletion" too) tuples of callables.
>
> I see it's clear how to modify the behaviour of the descriptor instance,
> but is he saying I need to mess with the descriptor magic methods so they
> know what applies to each instance?
If (e.g.) __set__ needs to behave differently when applied to certain
instances rather than others, then it had better be "messed with"
(overridden) compared to property.__set__ since the latter has no such
proviso. Of course, your architecture as sketched below (taking
advantage of the fact that property.__set__ always calls a certain
callable, and you get to control that callable) is OK too.
> ## my silly example
> class ObserverProperty(property):
> def __init__(self,name,observers=None,validator=None):
> self._name = name
> self._observers = observers or []
> self._validator = validator or (lambda x: x)
> self._pName = '_' + name
> property.__init__(self,
> fset=lambda inst, value: self.__notify_fset(inst,value),
> )
Why not just fset=self.__notify_fset ? I fail to see the added value of
this lambda. Anyway...:
> def __notify_fset(self,inst,value):
> value = self._validator(value)
> for obs in self._observers:
> obs(inst,self._pName,value)
> inst.__dict__[self._pName] = value
>
> def add(self,obs):
> self._observers.append(obs)
...this class only offers sets of observers *per-descriptor instance*,
not ones connected to a specific 'inst' being observed. My point is,
you could add the latter pretty easily.
> def obs0(inst,pName,value):
> print 'obs0', inst, pName, value
>
> def obs1(inst,pName,value):
> print 'obs1', inst, pName, value
>
> class A(object):
> x = ObserverProperty('x')
>
> a=A()
> A.x.add(obs0)
>
> a.x = 3
>
> b = A()
> b.x = 4
>
> #I wish I could get b to use obs1 instead of obs0
> #without doing the following
> class B(A):
> x = ObserverProperty('x',observers=[obs1])
>
> b.__class__ = B
>
> b.x = 7
You can, if you have a way to call, say, b.x.add_per_inst(b, obs1).
Such as, adding within ObserverProperty:
self._observers_per_inst = {}
in the init, and changing the notification method to do:
def __notify_fset(self,inst,value):
value = self._validator(value)
observers = self._observers_per_inst.get(inst)
if not observers: observers = self._observers
for obs in observers:
obs(inst,self._pName,value)
inst.__dict__[self._pName] = value
and a new method add_per_inst:
def add_per_inst(self, inst, obs):
self._observers_per_inst.setdefault(inst,[]).append(obs)
Of course, you most likely want to use weak rather than normal
references here (probably to both instances and observers), but that's a
separate issue.
Alex
More information about the Python-list
mailing list