Metaclasses vs. standard Python reflection?

Alex Martelli aleax at aleax.it
Sun May 4 13:28:13 EDT 2003


Robin Becker wrote:

> 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.

Ah!  OK, I wouldn't have guessed that.


>>> 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. ......

...nor that.  OK, clearer now, thanks.

>>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.

Yes, well, sorry for not testing this!-)

> I first get a missing self in the __setattr__ so that needs to be changed

Ah yes, that 'self' must be args[0], or else:

> to
>         def __setattr__(self,*args):
>                 return self.__hook(*args+self.__oldhook)

But if you choose this, then of course you need to pass the 'self'
again as the first argument to self.__hook (which I set on the
INSTANCE and therefore does no implicitly-insert-self magic - I
far prefer this, so that hook can be any callable whatsoever).

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

Most assuredly not!  Look again at the statement where we set __oldhook:

>>    inst._hooker_oldhook = getattr(inst,'__setattr__',None),

See?  That trailing comma means I have set a TUPLE in _hooker_oldhook.

Why build the singleton tuple over and over again at EACH call into
__setattr__ when we can build it once and for all?  What added value
could this possibly give?

If this is hard to read, then a comment would be appropriate, and/
or redundant parentheses to point out "this is a tuple" more clearly.

> 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.

Hmmm -- I haven't "messed with __setattr__" YET when I assign those two
per-instance attribute to inst -- I was assuming that, if inst does
already have a __setattr__, it must have a very good reason to, and
thus OF COURSE it will want to intercept (and sensibly handle) those
assignments just as well as any other.  If that's not the case (e.g. if
inst does have a __setattr__ but it's something crazy and wrong, which
must be bypassed -- very bad situation in which to work of course),
then you might substitute something like:

inst.__dict__['_hooker_oldhook'] = getattr(inst,'__setattr__',None),

However, in this case you should do that AFTER you've set:

>>    inst.__class__ = hooker

to ensure inst DOES have a __dict__ (it might not, were it a newstyle
instance from a class with __slots__).  Same for the other attribute
assignment, of course.

> 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

Hmmm, I seem to have received pretty messed-up line ends &c here,
but, anyway, roughly this does seem all right.

> but it performs marginally slower than the other versions

Weird.  Where does profiling show the extra time is going?  I don't
see any obvious waste of time happening here -- on the contrary, I
saw quite a few possible wastes in at least some of your versions
(e.g., you set BOTH the object's class AND its bases as the bases
for a new class you were creating -- so, when a not-found attribute
is looked for, duplication of lookup is guaranteed).


>>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?

Why wouldn't it?  Keeping state in objects has always worked.


> 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

No, this shouldn't work until 2.1 and even there would still
require the "from __future__ import".


Alex





More information about the Python-list mailing list