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.