#!/usr/bin/env python """ meta2and3.py Describes a simple approach to employing metaclasses in a manner simultaneously compatible with both Python 2 and 3. """ # The actual definition of the metaclass remains the same in both Python 2 and 3 class SillyWalksMetaClass(type): """ A metaclass describing a class that acts as a singleton and provides a registry of funny walking styles, something no Monty Python fan should be without. """ def __init__(self, class_name, bases, namespace): if not hasattr(self, 'walk_types'): self.walk_types = [] else: self.walk_types.append(self) def __str__(self): return self.__name__ def get_walks(self, *args, **kwargs): """Returns a list ordered by the class name""" return sorted(self.walk_types, key=lambda x: x.__name__) # Its the *use* of the metaclass which changes in 3. Never fear, # Knights who say Ni! are here... # in Python 2.x you'd create your metaclass derived class as: # # class SillyWalk(object): # """ # Subclasses implement a silly walk of stunning sillyness # """ # # __metaclass__ = SillyWalksMetaClass # As of Python 3 the __metaclass__ attribute means nothing; instead you # supply the class definition with a keyword: # # class SillyWalk(metaclass=SillyWalksMetaClass): # """ # Subclasses implement a silly walk of stunning sillyness # """ # AND NOW FOR SOMETHING COMPLETELY DIFFERENT... # But what we need is a method which works for both, since # Python 2.x will raise a SyntaxError which can't be hidden # behind an if or try block. The 2 *and* 3 solution is: SillyWalk = SillyWalksMetaClass('SillyWalk', (object, ), {}) # As you see, it was only a flesh wound. We have a simple # and clean approach for metaclasses for both Python 2 and # 3. # Subclassing remains the same for Python 2.x and Python 3+, # lets create four favorite silly walks: class Skip(SillyWalk): pass class LurchAndSkip(SillyWalk): pass class CleeseSpecial(SillyWalk): 'The quintessential silly walk' class HopWeaveLurchShudder(SillyWalk): pass # got all four? assert len(SillyWalk.get_walks()) == 4 # right then, lets have a look print(', '.join([str(walk) for walk in SillyWalk.get_walks()])) # Being a singleton, you'd expect the following to be identical: assert SillyWalk.get_walks() == HopWeaveLurchShudder.get_walks() # and it is... # $ python3.0 meta2and3.py # CleeseSpecial, HopWeaveLurchShudder, LurchAndSkip, Skip # $ python2.6 meta2and3.py # CleeseSpecial, HopWeaveLurchShudder, LurchAndSkip, Skip # $ python2.5 meta2and3.py # CleeseSpecial, HopWeaveLurchShudder, LurchAndSkip, Skip