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