Skip to content
Advertisement

How to pass arguments to the metaclass from the class definition?

I’m trying to dynamically generate classes in python 2.7, and am wondering if you can easily pass arguments to the metaclass from the class object.

I’ve read this post, which is awesome, but doesn’t quite answer the question. at the moment I am doing:

def class_factory(args, to, meta_class):
    Class MyMetaClass(type):
        def __new__(cls, class_name, parents, attrs):
            attrs['args'] = args
            attrs['to'] = to
            attrs['eggs'] = meta_class

    class MyClass(object):
        metaclass = MyMetaClass
        ...

but this requires me to do the following

MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()

Is there a cleaner way of doing this?

Advertisement

Answer

Yes, there’s an easy way to do it. In the metaclass’s __new__() method just check in the class dictionary passed as the last argument. Anything defined in the class statement will be there. For example:

class MyMetaClass(type):
    def __new__(cls, class_name, parents, attrs):
        if 'meta_args' in attrs:
            meta_args = attrs['meta_args']
            attrs['args'] = meta_args[0]
            attrs['to'] = meta_args[1]
            attrs['eggs'] = meta_args[2]
            del attrs['meta_args'] # clean up
        return type.__new__(cls, class_name, parents, attrs)

class MyClass(object):
    __metaclass__ = MyMetaClass
    meta_args = ['spam', 'and', 'eggs']

myobject = MyClass()

from pprint import pprint
pprint(dir(myobject))
print myobject.args, myobject.to, myobject.eggs

Output:

['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__metaclass__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'args',
 'eggs',
 'to']
spam and eggs

Update

The code above will only work in Python 2 because syntax for specifying a metaclass was changed in an incompatible way in Python 3.

To make it work in Python 3 (but no longer in Python 2) is super simple to do and only requires changing the definition of MyClass to:

class MyClass(metaclass=MyMetaClass):
    meta_args = ['spam', 'and', 'eggs']

It’s also possible to workaround the syntax differences and produce code that works in both Python 2 and 3 by creating base classes “on-the-fly” which involves explicitly invoking the metaclass and using the class that is created as the base class of the one being defined.

class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
    meta_args = ['spam', 'and', 'eggs']

Class construction in Python 3 has also been modified and support was added that allows other ways of passing arguments, and in some cases using them might be easier than the technique shown here. It all depends on what you’re trying to accomplish.

See @John Crawford’s detailed answer for a description of of the process in the new versions of Python.

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement