Metaclasses vs. standard Python reflection?

Robin Becker robin at jessikat.fsnet.co.uk
Sun May 4 05:17:48 EDT 2003


In article <wkWsa.38309$3M4.1072553 at news1.tin.it>, Alex Martelli <aleax at aleax.it>
writes
>Robin Becker wrote:
......
>
>What "anonymous class" are you taking about...?  I don't think there
>is any such thing -- and surely there isn't one in my code snippet
>up there in function hook_setattr.
...... I regard the inner dynamic hook class as anonymous even though it has a name,
I think its exact name doesn't matter so it might as well be anonymous.
>> difficult without dynamic scopes to pass in the old __setattr__ if any.
>
>What are "dynamic scopes", pray?
local scopes, in my poor mind the old scoping was static and easily defined, now we
seem to have a dynamic global scope although it's really just as static.
......
>def hook_setattr(inst, hook):
>    if hasattr(inst, '__proxy_setattr__'): return
>    class hooker(inst.__class__):
>        __proxy_setattr__ = 1
>        def __setattr__(*args):
>            return self.__hook(*args+self.__oldhook)
>    hooker.__name__ = inst.__class__.__name__
>    inst._hooker_hook = hook
>    inst._hooker_oldhook = getattr(inst,'__setattr__',None),
>    inst.__class__ = hooker
>
>How is this inferior to that intricate 'lambda'...?
...... well in the first place this doesn't work.

I first get a missing self in the __setattr__ so that needs to be changed to
        def __setattr__(self,*args):
                return self.__hook(*args+self.__oldhook)

and should that really be *(args+(self.__oldhook,)) ?

I also get errors because of the definition order. It's a bit dangerous to do inst.x
= when messing with __setattr__. I'm not sure how to rescue it as I start getting
some odd errors when I try and fix it further.
I rewrote it thusly and it seems to work

def hook_setattr2(obj, hook):
    if not hasattr(obj, '_hooker__hook'):
        class hooker(obj.__class__):
            def __setattr__(self,k,v):
                return self.__hook(self,k,v,self.__old_hook)
        hooker.__name__ = obj.__class__.__name__
        obj.__dict__['_hooker__old_hook'] = getattr(obj,'__setattr__',None)
        obj.__dict__['_hooker__hook'] = hook
        obj.__class__ = hooker

but it performs marginally slower than the other versions
>
>Sure.  That's not very different from what I'm doing in the
>latest version of hook_setattr, except that I avoid the weird
>approach of using a lambda to store state, when state is so
>much easier to keep in object attributes and def is such a
>simpler and smoother way to create functions than lambda.
>
>
this is fine in 2.2, but does it work for earlier versions?

def hook_setattr1(obj, hook):
        if not hasattr(obj,'__proxy__setattr__'):
                old_hook = getattr(obj,'__setattr__',None)
                class hooker(obj.__class__):
                        __name__ = obj.__class__.__name__
                        __proxy_setattr__ = 1
                        def __setattr__(self,k,v):
                                return hook(self,k,v,old_hook)
                obj.__class__ = hooker

.....
>There should be no problem with my function hook_setattr, no
>matter whether inst belongs to a new-style or old-style
>class.  Even if that class has __slots__, no problem -- the
>hooker class doesn't, so inst now has a dict, whether it had
>one previously or not, so we can safely store the hook and
>the old bound-method for __setattr__ if any.
>
>
>Alex
>
my test code looks like
#####################
def hook_setattr0(obj,hook):
    if not hasattr(obj,'__proxy__setattr__'):
        C = obj.__class__
        import new
        obj.__class__=new.classobj(C.__name__,(C,)+C.__bases__,
            {'__proxy__setattr__':1,
            '__setattr__':lambda self,k,v,osa=getattr(obj,'__setattr__',None):
hook(self,k,v,osa)})

def hook_setattr1(obj, hook):
    if not hasattr(obj,'__proxy__setattr__'):
        old_hook = getattr(obj,'__setattr__',None)
        class hooker(obj.__class__):
            def __setattr__(self,k,v):
                return hook(self,k,v,old_hook)
        hooker.__name__ = obj.__class__.__name__
        hooker.__proxy_setattr__ = 1
        obj.__class__ = hooker

def hook_setattr2(obj, hook):
    if not hasattr(obj, '_hooker__hook'):
        class hooker(obj.__class__):
            def __setattr__(self,k,v):
                return self.__hook(self,k,v,self.__old_hook)
        hooker.__name__ = obj.__class__.__name__
        obj.__dict__['_hooker__old_hook'] = getattr(obj,'__setattr__',None)
        obj.__dict__['_hooker__hook'] = hook
        obj.__class__ = hooker

def hook(self,k,v,osa):
    if k=='a':
        pass    #ignore
    elif osa:
        osa(k,v)
    else:
        self.__dict__[k] = v

class A:
    def __init__(self):
        self.__dict__['__internal__'] = {}
    def __setattr__(self,k,v):
        self.__internal__[k] = v

def run(n=1000000,hf=hook,shf=hook_setattr1):
    from time import time
    t0 = time()
    a=A()
    shf(a,hf)
    for i in xrange(n):
        setattr(a,'ab'[i%2],i)
    t = time()-t0
    print "n=%d hf=%s shf=%s total time=%.2f normalised time=%.6f" %
(n,hf,shf,t,t/n)

run(shf=hook_setattr0)
run(shf=hook_setattr1)
run(shf=hook_setattr2)
#####################
-- 
Robin Becker




More information about the Python-list mailing list