Metaclasses vs. standard Python reflection?

Alex Martelli aleax at aleax.it
Sat May 3 14:57:02 EDT 2003


<posted & mailed>

Robin Becker wrote:

> .....when trying to intercept/hook instance __setattr__ magic I find it
> extremely hard with mundane python.
> 
> ie
>>>> import new
>>>> class A: pass
> ...
>>>> a=A()
>>>> def hook(self,k,v):
> ...     print 'hook(%s,%s,%s)' % (repr(self),repr(k),repr(v))
> ...
>>>> a.__setattr__=new.instancemethod(hook,a,a.__class__)
>>>> a.c=3
>>>> a=A()
>>>>
> 
> we didn't hook __setattr__

a.c = 3 may trigger a.__class__.__setattr__(a, 'c', 3), but doesn't
go looking for a per-instance a.__setattr__.  More generally, do not
rely on per-instance special methods, as throughout the new-style
object model it's only special methods that live in *classes* that
participate in operations, NOT those set specifically on instances.


> Is this something that is easily handled using some metaclass approach?
> Currently I believe I have to do something complex  ie
> 
>>>> a=A()
>>>> class _HOOK:
> ...     def __setattr__(self,k,v):
> ...             print 'hook(%s,%s,%s)' % (repr(self),repr(k),repr(v))
> ...
>>>> a.__class__=new.classobj(a.__class__.__name__,a.__class__.__bases__+
> (_HOOK,),a.__class__.__dict__)
>>>> a.y=3
> hook(<__main__.A instance at 0x0120F298>,'y',3)
>>>>
> 
> and even this isn't quite right as a is not an instance of A (I guess we
> need to put a.__class__ into the bases).

Yes to the last point.  I'm not sure what it is that you find so
problematic here.  It can also be done far more simply of course:

def hook_setattr(inst):
    class hooker(inst.__class__):
        def __setattr__(*args):
            print 'hook(%r,%r,%r)' % args
    hooker.__name__ = inst.__class__.__name__
    inst.__class__ = hooker

class A: pass
a = A()
hook_setattr(a)
a.y = 3

class B(object): pass
b = B()
hook_setattr(b)
b.z = 4

This approach has the advantage of working for both classic AND
newstyle classes, too -- sometimes a substantial plus!


Since what you seem to be after is extremely dynamic -- hooking
a specific instance's __setattr__ on the fly, no less! -- I'm not
quite sure what you might be looking for from a custom metaclass,
which would need to be used to instantiate classes (that's all a
metaclass does, after all).  If you can act invasively on the
class A of the instances you want to hook, you might make that
class have a special __setattr__ which selectively delegates to
a given instance's hook iff the instance is hooked -- of course
you can inject that __setattr__ from A's metaclass, but offhand
there would seem to be little added value in doing that (even if
what you're after is reusing the mechanism, it seems it might be
reusable by plain old inheritance just as well as by using
custom metaclasses).  E.g., I have in mind A having a class-level
weakref.WeakKeyDict hooker, mapping (some of) its instances to a
hook callable, suitable methods to add/modify/remove entries
therein, and a __setattr__ such as:

    def __setattr__(self, attname, attval):
        hook = self.hooker.get(self)
        if hook is not None: return hook(self, attname, attval)
        self.__dict__[attname] = attval   # or whatever

but unless most instances of A are hooked most of the time, this
threatens to be a very heavyweight approach, slowing down every
attribute-setting on every unhooked instance quite significantly.

What exactly IS the problem you see with the hook_setattr approach?


Alex





More information about the Python-list mailing list