Metaclasses: Blueprints of Blueprints

We all know that objects are created from classes. But can a class be created from a class? Yes it can; this is called a metaclass. They give us a great deal of power when it comes to changing the behavior of a class. Python 2.2 supports metaclasses. Peyton McCullough explains.

Prerequisites

This article assumes that you are familiar with the new-style classes introduced in Python 2.2. If you are not familiar with these new classes, the article “More Object Orientation” is worth taking a look at. Likewise, if you are completely unfamiliar with object orientation in Python, the article “Object Orientation in Python” will familiarize you with the concept.

Blueprints of Blueprints

We are all familiar with the relationship between classes and objects: an object is created from a class. In this model, and I’m sure you have heard this analogy many times before, the class serves as a blueprint for the object. While this two-component model is fine for the majority of situations where object orientation is used, there is another component that you may or may not have heard of before. Let’s put that aside for just a second, though.

The creation of objects from classes is a fairly customizable process. Using a class, we can tailor an object to make it unique – to suit a specific purpose. For example, take a look at this class:

>>> class Style:
 def __init__ ( self, foreground, background, font, size ):
  self.foreground = foreground
  self.background = background
  self.font = font
  self.size = size

From the Style class, we can easily create numerous instances, each with its own unique attributes:

>>> plain = Style ( ‘black’, ‘white’, ‘Verdana’, 9 )
>>> ugly = Style ( ‘purple’, ‘green’, ‘Arial’, 18 )
>>> inverse = Style ( ‘white’, ‘black’, ‘Verdana’, 9 )
>>> christmas = Style ( ‘green’, ‘red’, ‘Arial’, 1 )
>>> halloween = Style ( ‘orange’, ‘black’, ‘Arial’, 10 )
>>> msOffice = Style ( ‘black’, ‘white’, ‘Times New Roman’, 12 )
>>> starOffice = Style ( ‘black’, ‘white’, ‘Thorndale’, 12 )

Each object is created exactly how we want it. We can also make this process dynamic, generating the objects without directly creating them from within the code:

>>> import random
>>> colors = [ 'black', 'white', 'green', 'red', 'orange', 'brown', 'yellow' ]
>>> fonts = [ 'Verdana', 'Arial', 'Times New Roman', 'Helvetica' ]
>>> styles = []
>>> for x in xrange ( 10 ):
 styles.append ( Style ( random.choice ( colors ), random.choice ( colors ), random.choice ( fonts ), random.randint ( 7, 20 ) ) )

Now let’s reintroduce that third component I was talking about earlier. If objects can be created uniquely and dynamically, and if classes are technically objects in Python, then can classes be created the same way? Does Python support blueprints of blueprints? The answer is yes. Python supports classes of classes. These are called metaclasses, and their instances are classes themselves.

{mospagebreak title=The Barebones}

Metaclasses are created just like classes, except they are a subclass of type or another metaclass. Actually, type is itself a metaclass. There isn’t any interesting syntax involved with their creation:

>>> class MetaClass ( type ):
 pass

Now, to demonstrate how we can use our overly-primitive metaclass, let’s create a class from it. There are two ways to do this. The first way is to create a class from our metaclass just as we would create an object from a normal class. However, we have to make a few changes, or we’ll end up with this:

>>> A = MetaClass()

Traceback (most recent call last):
  File “<pyshell#40>”, line 1, in -toplevel-
    A = MetaClass()
TypeError: type() takes 1 or 3 arguments

Unless you instruct them to do otherwise, metaclasses accept three arguments: the name of the class to be created, a tuple of the base classes involved and a dictionary containing the class’s attributes:

>>> A = MetaClass ( ‘A’, (), {} )
>>> A
<class ‘__main__.A’>

We can see our metaclass of our class like this:

>>> A.__class__
<class ‘__main__.MetaClass’>

Similarly, we can check the metaclass of an object like so:

>>> z = A()
>>> z.__class__.__class__
<class ‘__main__.MetaClass’>

Creating some attributes is not very complicated. Let’s create a class with a few variables:

>>> B = MetaClass ( ‘B’, (), { ‘p': 4, ‘q': ( ‘1’, 2, ‘3’ ), ‘r': “%” } )
>>> B.p
4
>>> B.q
(‘1′, 2, ‘3’)
>>> B.r
‘%’

Of course, this is pretty annoying and pointless for anything complex, which leads us to the second method of creating classes from metaclasses. You can simply assign the metaclass to a new-style class’s __metaclass__ variable:

>>> class C ( object ):
 __metaclass__ = MetaClass

This is a more practical method of creating classes from metaclasses than the previous method.

{mospagebreak title=Adding Some Meat}

Now let’s add some more substance to the picture, since the purpose of metaclasses is not to just sit there and look pretty. We can start by sticking some variables into our metaclass:

>>> class MetaA ( type ):
 x = 4
 y = 19
 z = ( 1, 2, 3 )

The classes created out of this metaclass will have the same variables:

>>> class A ( object ):
 __metaclass__ = MetaA


>>> A.x
4
>>> A.y
19
>>> A.z
(1, 2, 3)

However, this isn’t a very good use at all for metaclasses, since a regular class could do the same thing if A were to subclass it, and the process would be a bit simpler. However, just as a normal class can initialize an object, a metaclass can initialize a class:

>>> class MetaB ( type ):
 def __init__ ( cls, name, bases, dct ):
  print cls
  print name
  print bases
  print dct

As you can see, the __init__ method accepts four variables: cls, name, bases and dct. The cls variable can be compared to self in normal classes. However, since we are dealing with a class instead of an object, it is named a bit differently. The name variable simply contains the name of the class, the bases variable contains a tuple of base classes, and the dct variable contains a dictionary of attributes. Another common name for this last variable is dict, though that gets in the way of the dictionary type since they share the same name in that situation. The __init__ method is called as soon as our class is created:

>>> class B ( object ):
 __metaclass__ = MetaB
 x = “c”
 z = 1
 y = z + 3
 def __init__ ( self ):
  print z + y
 def test ( self ):
  print x

  
<class ‘__main__.B’>
B
(<type ‘object’>,)
{‘__module__': ‘__main__’, ‘__metaclass__': <class ‘__main__.MetaB’>, ‘test': <function test at 0x00B4A570>, ‘y': 4, ‘x': ‘c’, ‘z': 1, ‘__init__': <function __init__ at 0x00B4A2B0>}

The MetaB class simply displays the name of each variable. As you can see above, the dictionary of attributes provides us with an interesting link to the class’s internals. This is a significant feature, and it gives us a lot of power over classes. It allows us to make some pretty interesting modifications to things that would not otherwise be possible. Consider class C:

>>> class C:
 def one ( self ):
  return True
 five = four = three = two = one

Now, suppose that we didn’t want to create that chain at the end, which assigns alternate names for the one method. We could use a metaclass to eliminate the need for that. For example, we could define the method as multi_one_two_three_four_five and have a metaclass rip it apart for us, creating methods one through five. It’s actually pretty simple:

>>> class MetaD ( type ):
 def __init__ ( cls, name, bases, dct ):
  # Loop through the dictionary
  for key, value in dct.iteritems():

   # If the key starts with multi_, perform our work
   if key.find ( ‘multi_’ ) == 0:

    # Split the name to get the aliases
    aliases = key.split ( ‘_’ ) [ 1: ]

    # Define the new names
    for alias in aliases:
     setattr ( cls, alias, value )

We simply loop through the dictionary, and if the attribute name starts with “multi_”, then we break it apart at each underscore and re-assign the value to each piece of the name. Here’s our metaclass in action:

>>> class D ( object ):
 __metaclass__ = MetaD
 def multi_one_two_three_four_five ( self ):
  return True


>>> x = D()
>>> x.one
<bound method D.multi_one_two_three_four_five of <__main__.D object at 0x00B4C3D0>>
>>> x.two
<bound method D.multi_one_two_three_four_five of <__main__.D object at 0x00B4C3D0>>
>>> x.three()
True

Metaclass MetaD might not be completely practical, but it demonstrates the power of metaclasses. We can use metaclasses to alter the behavior of classes, allowing for new levels of control over classes.

{mospagebreak title=Using Metaclasses}

You’re probably wondering when and where to use metaclasses. While there is no rule that tells us exactly that, we can look over a few situations where metaclasses can be used.

We’ll start with a widely-used example, and then we’ll build some features onto it to form more examples. Take a look at class E:

>>> class E:
 pass

If we try to print the class out, we get a string representation of E:

>>> print E
__main__.E

What if we want to change the output to something more informative, though? This requires changing the behavior of the class. The words “changing the behavior” should have sounded an alarm in your mind. Metaclasses are perfect for changing the behavior of a class.

If we were performing our work on an object, we would simply define a __str__ method in the parent class. Since we’re working with a class, though, we need to define the method in the metaclass. The method needs to return a string with the description we want:

>>> class MetaF ( type ):
 def __str__ ( cls ):
  return “A class made from metaclass MetaF”

We have now achieved the desired behavior:

>>> class F ( object ):
 __metaclass__ = MetaF


>>> print F
A class made from metaclass MetaF
>>> str ( F )
‘A class made from metaclass MetaF’

We can also go above and beyond, though. Instead of having a generic message to describe what may be an entire set of classes, we can give each class the capability to define a custom message. The message can be stored in a predefined variable that each class can change. This variable can be returned in our metaclass’s __str__ method. Take a look at metaclass MetaG:

>>> class MetaG ( type ):
 __nameString__ = ‘A class made from metaclass MetaG’
 def __str__ ( cls ):
  return cls.__nameString__

Each class made from MetaG will contain the variable __nameString__ and can change it. When we request a string representation of each class, its __nameString__ variable will then be returned:

>>> class G ( object ):
 __metaclass__ = MetaG


>>> print G

A class made from metaclass MetaG

>>> class G ( object ):
 __metaclass__ = MetaG
 __nameString__ = ‘A class named G’


>>> print G

{mospagebreak title=A class named G}

What if we wanted to return a “map” of a class’ attributes? We would need to loop through the attribute dictionary and format the contents. Instead of redoing the __str__ method, though, we will redo __repr__. This way, the contents of an instance can be spilled out in the command line by just typing the instance class’ name, rather than attaching print. MetaH does all of this for us:

>>> import types
>>> class MetaH ( type ):

 # Create a dictionary variable to store the attributes in
 # We want to use the attribute dictionary in __repr__
 dct = None
 
 def __init__ ( cls, name, bases, dct ):
  cls.dct = dct
 def __repr__ ( cls ):

  # Create a list to store methods in
  methods = []
  
  # Create another to store variables in
  variables = []
  
  # Loop through the attributes and add them
  for key, value in cls.dct.iteritems():
   if type ( value ) == types.FunctionType:
    methods.append ( key )
   else:
    variables.append ( key )

  # Sort the lists
  methods.sort()
  variables.sort()
  
  # Create a string to store the “map” in
  map = “Class Map”

  # Put everything in the map
  map = map + “nnMETHODS:”
  for method in methods:
   map = map + “n” + method
  map = map + “nnVARIABLES:”
  for variable in variables:
   map = map + “n” + variable
   
  # Return the map
  return map

The first thing that MetaH does is create a variable to house the dictionary of attributes. The dictionary of attributes is moved to the variable in the __init__ method. We then create lists to store the class’ methods and variables in the __repr__ method, and we loop through the dictionary to see what attribute belongs where. Finally, we sort the lists, create a string and then return that string. Here’s our metaclass in action:

>>> class H ( object ):
 __metaclass__ = MetaH
 a = 1
 b = 2
 c = 3
 def d ( self ):
  pass
 def e ( self ):
  pass
 def f ( self ):
  pass


>>> H
Class Map

METHODS:
d
e
f

VARIABLES:
__metaclass__
__module__
a
b
c

Wrapping It Up

You should now know what metaclasses are and should have an idea of what situations call for the use of metaclasses. Metaclasses are, in the simplest definition, blueprints of blueprints. The relationship between a metaclass and a class is just like the relationship between a class and an object. A class changes the behavior of an object, and a metaclass changes the behavior of a class.

Although metaclasses are not used very often, they are powerful devices when they are used. They can change the internals of a class, affecting its behavior in ways not normally possible. They can also be used to generate classes dynamically, just as objects can be created dynamically from classes.

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort