Programmer's Python - Metaclass |
Written by Mike James | ||||
Wednesday, 13 August 2025 | ||||
Page 2 of 3
Meta __new__ and __init__So after the namespace has been constructed, the class object is constructed by the system making an implicit call: MyClass = MyMeta("MyClass",(object,), ns) Notice that this is a call to MyMeta, which is a class even if it does inherit from type, so what happens is exactly what always happens – first __new__ is called and then __init__. This is also what happens when you make a direct call to: type("MyClass",(object,),ns) If MyMeta doesn’t have a definition for __new__ or __init__ the calls are passed on to the same methods defined on type or the next metaclass in the inheritance chain, see Chapter 12. If you want to customize the metaclass then you have to provide implementations of __new__ or __init__. The purpose of both methods is exactly the same as in the case of a class creating an instance and they are called with the same parameters as passed to the metaclass, i.e. "MyClass",(object,), ns. Notice that metaclass __new__ and __init__ are called by default with more parameters than class __new__ and __init__. As for an instance object you use __new__ to create the instance of the class and __init__ to customize it. This isn’t strictly the case because you can do your initialization in __new__ after constructing the class object and before returning it, but it is a good division of labor and worth trying to keep to. So, the default behavior provided by type can be implemented using explicit __new__ and __init__ definitions: class MyMeta(type): def __new__(self,name,base,ns): return type.__new__(self,name,base,ns) def __init__(self,name,base,ns): type.__init__(self,name,base,ns) It is more usual to use cls for class rather than self, but using self does make sure that it is clear that there is nothing new here. You can use this default code to start customizing the metaclass: class MyMeta(type): def __new__(self,name,base,ns): print("meta new") return type.__new__(self,name,base,ns) def __init__(self,name,base,ns): print("meta init") self.cloned = False type.__init__(self,name,base,ns) class MyClass(metaclass=MyMeta): myAttribute=21*2 If you run this you will see: meta new meta init generated as soon as the system reaches the class definition, i.e. not when the class is called. It would be best practice to use super().__new__ and super().__init__, but in this case we need it to be clear that it is type that is doing the work. You can also pass additional keyword parameters to the metaclass when it is used in a class definition. For example: class MyClass(metaclass = MyMeta,myParameter = 10): means that the __init__ and __new__ will be called with parameters: self, name, base, ns, myParameter = 10 You can use any parameters you care to invent, but make sure that the metaclasses’ methods accept them even if they don’t use them. Notice that the metaclass = marks the end of the list of base classes. That is: class MyClass(A,B,metaclass = MyMeta, args) MyClass inherits from class A and B, uses MyMeta as its metaclass and passes args to __new__ and __init__. For example, suppose you want MyClass to always have a cloned attribute that is set to True or False to indicate that it has been cloned: class MyMeta(type): def __init__(self,name,base,ns): self.cloned = False type.__init__(self,name,base,ns) Notice that this leaves the default type.__new__ to create the class object and __init__ just customizes it. Now you can use MyClass to create an instance: class MyClass(metaclass = MyMeta): myAttribute = 21*2 myObject = MyClass() It is important that you don’t confuse the metaclass __init__ with the class __init__. The first is called when the class is implemented, i.e. when the Python system reaches the class definition. The second is called when you use the class to create an instance. If you understand this you can immediately see that the metaclass __init__ is only called once, but the class __init__ is called once for each instance. It should also be obvious that the cloned attribute is an attribute of the class and not an instance – it is converted into an instance attribute when assigned to as an instance attribute: myObject.cloned = True You can also override the __new__ metaclass method to modify how the class instance is created, but this is not a common requirement.
__prepare__A well as __new__ and __init__, metaclasses also have a __prepare__ magic method which is automatically called to create the namespace data structure. This method isn’t often needed and its main purpose is to allow the use of a modified dictionary to order the namespace in some way. It is called with the cloned name and bases and it returns an empty ordered mapping. It is called before the evaluation of the class body and its sole purpose is to provide an empty data structure to store the namespace. The object returned by __prepare__ only has to support a minimal set of dictionary-like operations. It has to support adding and retrieving values, but other operations are optional. A simple example demonstrates how both __prepare__ and __new__ can be used to create a class that keeps its attributes in the order they were created. This is the example given as motivation for introducing __prepare__ in the documentation, but it has been overtaken by the upgrade of the standard dict. Even though it is no longer needed, it still makes a good example: import collections class OrderedClass(type): def __prepare__(name, bases, **kwds): return collections.OrderedDict() def __new__(cls, name, bases, namespace, **kwds): result = type.__new__(cls, name, bases, dict(namespace)) result.members = tuple(namespace) return result The __prepare__ is called before the class body is executed and it simply returns an OrderedDict data structure rather than a dictionary. An OrderedDict keeps the order of the key/values as they are entered. Of course, now a standard dict does the same thing. Next the body of the class is executed and the results are stored in the OrderedDict returned by __prepare__. Next __new__ is called in the usual way, but now the namespace is an OrderedDict and not just a dictionary. The call to type.__new__ creates the class instance, but notice that we need to convert the namespace into a dictionary. Finally, the OrderedDict is converted to a tuple and assigned to an attribute. Any class that uses OrderedClass as its metaclass: class A(metaclass = OrderedClass): def one(self): pass def two(self): pass def three(self): pass def four(self): pass print(A.members) will have a members attributes that gives the attributes in the order that they were defined: ('__module__', 'one', 'two', 'three', 'four') Notice that in this case the __new__ method was used to modify the class instance, something we usually expect __init__ to do. So to summarize:
|
||||
Last Updated ( Wednesday, 13 August 2025 ) |