metaclass confusions

Manuel M. Garcia mail at manuelmgarcia.com
Sat Feb 1 22:29:11 CET 2003


I am writing a program to calculate moves on a Rubik's cube toy.  I
wanted to have classes that kept references to all the instances
created, and when an instance was equivalent to one created before,
you would just get the reference to the first one created.  So
throughout the program, cubeface('up','south','east') would always
give a reference to the exact same instance, for example.

I have some test code that gives this functionality.  It seems pretty
straightforward and Pythonic, so I am happy, but whenever I do
anything with __new__, __metaclass__, staticmethod, classmethod, I
find the online documentation to be sorely lacking.  I always end up
puzzling through Guido's "Unifying types and classes in Python 2.2"
and a dozen different Alex Martelli posts to c.l.p.

(Alex, does your Nutshell book have a section explaining the details
of this stuff?  If it does, I promise to pre-order 3 copies from
Amazon: one for work, one for home, and one to put under my pillow
when I sleep!  ;-)

Anyway, here is the code:

# ############################### #

def print_do(a):
    """not important, just to print out results"""
    i = 0
    for s in a.split('\n'):
        s = s.strip()
        if s:
            i += 1
            try:
                 e = eval(s)
            except SyntaxError:
                print '%03i: %s' % (i, s)
                exec s
            else:
                print '%03i: %s: %r' % (i, s, e)
        else:
            print

class meta0(type):
    
    def __init__(cls, classname, bases, classdict):
        cls.all_dict = {}

    # this also worked, but is this overkill?        
    ##def __new__(cls, classname, bases, classdict):
    ##    classdict['all_dict'] = {}
    ##    return type.__new__(cls, classname, bases, classdict)
    
    # neither of these two worked...
    ##def __init__(cls, classname, bases, classdict):
    ##    classdict['all_dict'] = {}
    ##def __init__(cls, classname, bases, classdict):
    ##    cls.__dict__['all_dict'] = {}

class klass0(object):
    
    __metaclass__ = meta0
    
    def __new__(cls, a):
        
        # can use this instead of metaclass
        ##try:
        ##    cls.all_dict
        ##except AttributeError:
        ##    cls.all_dict = {}
        
        if cls.all_dict.has_key(a):
            return cls.all_dict[a]
        else:
            n = object.__new__(cls)
            n.count = len(cls.all_dict)
            cls.all_dict[a] = n
            return n
        
    def __init__(self, a):
        self.a = a
        
    def __str__(self):
        d = self.__dict__.copy()
        del d['a']
        return '<%s %r (%s) %r>' % (
            self.__class__.__name__,
            self.a,
            hex(id(self)),
            d )
    
    def __repr__(self): return str(self)

print_do("""
    class myclass0(klass0): pass
    class myclass1(klass0): pass

    x0 = myclass0('apple')
    y0 = myclass0('grape')
    z0 = myclass0('apple')
    x1 = myclass1('grape')
    y1 = myclass1('lemon')
    z1 = myclass1('grape')

    x0.c = 4
    y0.c = 5
    z0.c = 6
    x1.c = 7
    y1.c = 8
    z1.c = 9
    
    x0.c
    y0.c
    z0.c
    x1.c
    y1.c
    z1.c
    
    x0.d = 123456 
    x1.d = 234567 
    
    x0
    y0
    z0
    x1
    y1
    z1
    """)

-----------------------------------

003: x0 = myclass0('apple')
004: y0 = myclass0('grape')
005: z0 = myclass0('apple')
006: x1 = myclass1('grape')
007: y1 = myclass1('lemon')
008: z1 = myclass1('grape')

023: x0: <myclass0 'apple' (0x11a6aa0)
    {'count': 0, 'c': 6, 'd': 123456}>
024: y0: <myclass0 'grape' (0x11692d8)
    {'count': 1, 'c': 5}>
025: z0: <myclass0 'apple' (0x11a6aa0)
    {'count': 0, 'c': 6, 'd': 123456}>
026: x1: <myclass1 'grape' (0x11a4ab0)
    {'count': 0, 'c': 9, 'd': 234567}>
027: y1: <myclass1 'lemon' (0x11a6ac8)
    {'count': 1, 'c': 8}>
028: z1: <myclass1 'grape' (0x11a4ab0)
    {'count': 0, 'c': 9, 'd': 234567}>

-----------------------------------

It works, and this is the code I will go with, but I had some
questions.

1) In the metaclass, I now think I understand the difference between
__new__ and __init__.  Would a metaclass even have both __new__ and
__init__ defined, and if so why?  Would a metaclass ever have any
other methods defined?  I guess not, because a metaclass only comes
into play during the creation of a subclass, so how could those other
methods ever get run?

2) I think this is the correct way to use metaclass: to run code just
once at the creation of a subclass, as opposed to code that has to be
run with every instance creation.  Is this more or less the only
reason to have a metaclass?  In general, it is hard for me to get my
head around exactly what is the difference between a base class and a
metaclass.

3) klass0 has __new__ and __init__ defined.  When __new__ recognizes a
instance creation argument, it returns an instance from before.
__init__ gets run against this instance.  Nothing bad happens, because
__init__ is cheap to run, and does nothing to destroy data already in
the instance.  Am I right in thinking that there is no way for __new__
to tell __init__ it doesn't need to run, except with some ad-hoc
techniques with special purpose attributes of the instance?

Manuel




More information about the Python-list mailing list