Metaclasses vs. standard Python reflection?

Alex Martelli aleax at aleax.it
Tue May 6 14:55:53 EDT 2003


Dave Benjamin wrote:
   ...
> On the surface, this technique seems to achieve the desired result.
> However, I have a hunch this will not work properly with inheritance. For
> instance, if another class extends the Simple class before I rebind it, I
> will have to apply Logger to those classes as well.

Yes, the exact technique you've chosen won't work with inheritance
(operating on __getattribute__, however, would).


> How could I achieve this effect with the global __metaclass__? I've tried
> doing things like:
> 
> __metaclass__ = Logger
> Simple.__metaclass__ = Logger
> 
> to no effect. I think I'm still very confused about how the metaclass
> mechanism works.

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

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.

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


Alex





More information about the Python-list mailing list