A new metaclass pattern, class methods

Thomas Heller theller at python.net
Wed May 23 18:26:59 CEST 2001


Guido's metaclass hook in Python goes this way:

If a base class (let's better call it a 'base object')
has a __class__ attribute, this is called to create the
new class.

>From demo/metaclasses/index.html:

class C(B):
    a = 1
    b = 2

Assuming B has a __class__ attribute, this translates into:

C = B.__class__('C', (B,), {'a': 1, 'b': 2})

Usually B is an instance of a normal class.
So the above code will create an instance of B,
call B's __init__ method with 'C', (B,), and {'a': 1, 'b': 2},
and assign the instance of B to the variable C.

I've ever since played with this metaclass hook, and
always found the problem that B would have to completely
simulate the normal python behaviour for classes (modifying
of course what you want to change).

The problem is that there are a lot of successful and
unsucessful attribute lookups, which require a lot
of overhead when implemented in Python: So the result
is very slow (too slow to be usable in some cases).

------

Python 2.1 allows to attach attributes to function objects,
so a new metaclass pattern can be implemented.

The idea is to let B be a function having a __class__ attribute
(which does _not_ have to be a class, it can again be a function).

What is the improvement?
Classes, when called, create new instances of themselves,
functions can return whatever they want.

I've used this pattern to realize the ideas Costas Menico
described in an article 'Simulating class' in c.l.p,
and James Althoff improved in a followup.

The proposal was to create class methods the following way:

<--- start of code --->
class Class1MetaClass: # Base for metaclass
    
    # Define "class methods" for Class1

    def whoami(self):
        print 'Class1MetaClass.whoami:', self

    # define Class1 & its "instance methods"

    class Class1: # Base class

        def whoami(self):
            print 'Class1.whoami:', self

Class1Meta = Class1MetaClass() # Make & name the singleton metaclass
instance
Class1 = Class1Meta.Class1     # Make the Class1 name accessible

# define subclasses:
class Class2MetaClass(Class1MetaClass):
    [rest of code omitted]

# use them:

Class1Meta.whoami() # invoke "class method" of base class
Class1().whoami()   # make an instance & invoke "instance method"
i = Class1Meta()    # make another instance...
i.whoami()          # ...invoke "instance method"
<--- end of code --->

I find this idea very interesting, but you have
to be very verbose: Define a Class1MetaClass, create
an instance to use as the metaclass, remeber to use
Class1MetaClass (and not! Class2Meta) to define
subclasses.

------

I would like (and have implemented) the following way
to create class methods. You have to supply the magic
MetaMixin object as the first object in the base class list.

class SpamClass(MetaMixin):
    # define "class methods"
    def whoami(self):
        print "SpamClass.whoami:", self

    def create(self, arg1, arg2):
        # a factory class method
        return self._instance(arg1, arg2)

    class _instance_:
        # define "instance methods"
        def whoami(self):
            print "instance.whoami:", self

# Subclassing goes this way:

class FooClass(MetaMixin, SpamClass):
    def create(self, arg1, arg2):
        # override the factory method
        return self._instance_(arg2, arg1)

    class _instance_(SpamClass._instance_):
        # define "instance methods"
        def blah(self):
            print "blah:", self
            self.whoami()

# Test them:

print SpamClass
#prints: <test.SpamClass instance at 007C0D84>

SpamClass.whoami()
#prints: SpamClass.whoami: <test.SpamClass instance at 007C0D84>

s = SpamClass()
print s
#prints: <__main__.SpamClass_Instance instance at 007C0DAC>

s.whoami()
#prints: instance.whoami: <__main__.SpamClass_Instance instance at
007C0DAC>

------

Here is finally the code for MetaMixin:

<--- start code --->
def MagicObject(name, bases, dict):
    import types, new
    l = []
    for b in bases:
        if type(b) == types.FunctionType:
            # we will see our MetaMixin function here,
            # but this cannot be used in bases
            continue
        if type(b) == types.InstanceType:
            # 
            l.append(b.__class__)
        else:
            l.append(b)
    bases = tuple(l)

    # define a new class
    Class = new.classobj(name, bases, dict)

    # create an instance of this class
    # without calling it's __init__ method
    class_instance = new.instance(Class, {})

    # new protocol for initializing
    try:
        class_instance.__init_class__
    except:
        pass
    else:
        class_instance.__init_class__()

    Instance = new.classobj("%s_Instance" % name, \
                              Class._instance_.__bases__, \
                              Class._instance_.__dict__)
    
    Instance.__dict__['__meta__'] = class_instance
    Class._instance_ = Instance
    
    return class_instance

def MetaMixin():
    pass
MetaMixin.__class__ = MagicObject

<--- end code --->



Comments?

Thomas



More information about the Python-list mailing list