Metaclasses vs. standard Python reflection?

Dave Benjamin ramen at lackingtalent.com
Tue May 6 15:41:58 EDT 2003


In article <ZyTta.91215$K35.2547601 at news2.tin.it>, Alex Martelli wrote:
> For example, say in logger.py you only have the following code:
> 
> def log_call(method):
>     def new_method(self, *args, **kwds):
>         print 'Entering %s...' % method
>         result = method(self, *args, **kwds)
>         print 'Exiting %s...' % method
> 
>     return new_method

Just in case another reader tries this code, I should mention that I forgot
a return statement in new_method. After that second print statement, there
should be another line that says "return result". Otherwise, this technique
will clobber all return values from methods. ;)

For the cut-and-pasters:

def log_call(method):
    def new_method(self, *args, **kwds):
        print 'Entering %s...' % method
        result = method(self, *args, **kwds)
        print 'Exiting %s...' % method
        return result
        
    return new_method

> class Logger(type):
>     def __new__(cls, name, bases, dict):
>         for key in dict:
>             if callable(dict[key]):
>                 dict[key] = log_call(dict[key])
>                     
>         return type.__new__(cls, name, bases, dict)
> 
> and simple.py is unchanged (except I lowercased the filename:-).
> 
> Now, for example:
> 
> Python 2.3b1+ (#10, May  3 2003, 20:20:32)
> [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
> Type "help", "copyright", "credits" or "license" for more information.
>>>> import logger
>>>> import types
>>>> simple = types.ModuleType('simple')        # make a new empty module
>>>> simple.__metaclass__ = logger.Logger       # set its __metaclass__
>>>> execfile('simple.py', simple.__dict__)     # and now populate it
>>>> simple.Simple.__class__                    # check we have the metacl
><class 'logger.Logger'>
>>>> x = simple.Simple(23)                      # make an instance
> Entering <function __init__ at 0x402af534>...
> Exiting <function __init__ at 0x402af534>...
>>>> x.print_x()                                # call its method
> Entering <function print_x at 0x402af56c>...
> x = 23
> Exiting <function print_x at 0x402af56c>...
>>>> class lessimple(simple.Simple):            # now let's subclass
> ...   def somethingelse(self): print 'boo!'
> ...
>>>> y = lessimple(42)                          # subclass instance
> Entering <function __init__ at 0x402af534>...
> Exiting <function __init__ at 0x402af534>...
>>>> y.somethingelse()                          # and ITS method
> Entering <function somethingelse at 0x402af614>...
> boo!
> Exiting <function somethingelse at 0x402af614>...
>>>>
> 
> 
> Does this help...?  To use the per-module __metaclass__ in a module
> that wasn't written for this, you have to use some trick such as this
> one (there may be many others too).  Afterwards, __metaclass__ gets
> inherited.  Remember to ALSO bind the name 'object' (to an empty
> class with your metaclass) to get classes that are newstyle because
> they inherit from object.

Yes! Thank you! =)

I managed to bind "object" this way - is this how you would do it?:
simple.object = simple.__metaclass__('object', (), {})

> This won't work for classes that inherit from other builtin types
> or declare their own __metaclass__ in their body -- THOSE you have
> to fixup post facto (with some subtlety in the latter case -- you
> will need to dynamically generate a suitable metaclass inheriting
> from the explicitly set one AND take care about conflicts in the
> intended semantics, particularly in __new__ -- indeed SOME conflicts
> might possibly be insoluble).

It also assumes you're only applying a single aspect, "Logger" in this case.
To apply multiple aspects, it would probably be a good idea to have a single
metaclass that dispatches...

Well, that was very helpful. Thanks for taking the time to answer that. =)

Take care,
Dave




More information about the Python-list mailing list