metaclass and customization with parameters

Carlos Ribeiro carribeiro at gmail.com
Sun Oct 3 18:24:41 EDT 2004


On 2 Oct 2004 20:10:04 -0700, zipher <zondervanz at gmail.com> wrote:
> After searching through comp.lang.python and the web regarding
> metaclasses, I could not find an example for customing classes using
> metaclass parameters.
> 
> I want to be able to create a class at runtime by calling some
> function or 'meta-constructor' which returns a customized class and
> sets a class attribute according a given parameter.

I'm not a metaclass expert, but I think I can share a few useful tips
with you. I've been studying metaclasses lately, and I've fallen on
some traps that seem to be common. I apologize if my tone sound
pretentious at times -- my intention is probably better than my words.

Let's start from the very beginning. In opposition to what happens at
other widely known languages -- C, for example -- class definitions in
Python are actually a two-step process. First the class code is
*executed*; when it finishes executing, the class is created by
calling its *metaclass*.

Step 1 - Executing class declaration code
------------------------------------------------
For simple classes -- the ones with just a few def's -- the execution
step seems to be the same as with class declarations in other
languages. That happens because def's are not executed at this time -
def's are only compiled. But any code inside a class definition that
is *not* shielded inside a method is executed, and can have side
effects on the way the class is created. One can do funny things with
it. Alex Martelli has show some examples on a previous thread -- for
example a metaclass doesn't need to be a class, any callable will do,
and it's possible to do weird things with it. The following code is
such a sample:

>>> class MyClass:
... 	for i in range(10):
... 		def newmethod(self,i=i):
... 			print "I'm method #%d" % i
... 		newname = "method%d" % i
... 		locals()[newname] = newmethod
... 
>>> mc = MyClass()
>>> mc.method0()
I'm method #0
>>> mc.method1()
I'm method #1

Although it doesn't involve metaclasses at all, it shows how dynamic
the class creation process is, and how much control one can have with
it. There are two worthy comments to make:

1) the locals() dict is the key at this step of class creation. The
local dict is one of the parameters passed to the metaclass. If you
fill the locals() with values -- either manually, as in the example
above, or by assigning values to local vars -- all the values will be
part of the class definition.

2) the signature of "def newmethod(self,i=i)" shows another
interesting side effect. The value if <i> (defined in the for loop) is
passed as a keyword paramenter, and is evaluated whenever the 'def'
statement is found. Because it changes, the def is re-run at every
loop. In the lack of this hack, the def would run only at the first
time, and all methods would in fact be the same.

Step 2 - Creating the new class
-----------------------------------------
The second step is the crucial one. A class is created from three
parameters: it's name, a list of base classes, and it's dict. The
following sample shows how to create a new class, pretty much as
you've asked:

>>> T = type('NewClass', ( ), {'x':1})
>>> T.x
1

By calling type() with carefully constructed parameters, it's possible
to create the class the way you want it -- including all the methods
and attributes you can possibly need. You can also supply the base
class, and it can be a dict, as you wish:

>>> T = type('newdict', (types.DictType,), {'x':1, 'y':2})
>>> t = T()
>>> t.x
1
>>> t['teste'] = 4
>>> t
{'teste': 4}
>>> t.items()
[('teste', 4)]
>>> 


Things that you can't do
---------------------------
Unfortunately, and even with all the flexibility that the process
have, there are a few things that are still difficult (or impossible)
to do. For example, there is no (easy) way to intercept the creation
of new names during the 'step 1', which means that some hacks need to
be done only after this step is completed. One such example is the
recently requested method overload' recipe.

It's also possible to rename a class that is being defined (by
changing its name inside the metaclass); however, it will still be
bound to the name stated in the source code. For  example:

class NewClass:
    def __metaclass__(name, bases, dct):
        return type('__%s__' % name, bases, dct)

>>> NewClass
<class '__main__.__NewClass__'>



-- 
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: carribeiro at gmail.com
mail: carribeiro at yahoo.com



More information about the Python-list mailing list