Programmer's Python - Metaclass |
Written by Mike James | |||
Monday, 09 July 2018 | |||
Page 2 of 2
Meta __new__ and __init__So after the namespace has been constructed, the class object is constructed by the system making an implicit call:
Notice that this is a call to MyMeta which is a class, 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:
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 metaclass next up in the inheritance chain – see Chapter 11. 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 with more parameters than class __new__ and __init__. 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:
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:
If you run this you will see:
which are generated as soon as the system reaches the class definition i.e. not when the class is called. Notice that 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. Notice that you can also pass additional keyword parameters to the metaclass when it is used in a class definition. For example:
means that the __init__ and __new__ will be called with parameters:
You can use any parameters you care to invent but make sure that the metaclass’s methods accept them even if they don’t use them. For example, suppose you want all MyClass to always have a cloned attribute that is set to true or false to indicate that it has been cloned:
Notice that this leaves type.__new__ to create the class object, __init__ just customizes it. Now you can use MyClass to create an instance:
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 to 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:
You can also override the __new__ metaclass method to modify how the class instance is created but this is less common a 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. It is called with the 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 __prepare__ 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. The example given in the standard Python documentation shows how both __prepare__ and __new__ can be used:
The __prepare__ is called before the class body is executed and it simply returns an OrderedDict data structure rather than a dictionary. An OrderedDict works exactly like a dictionary but it keeps the order of the key/values as they are entered. 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 namespace into a dictionary. Finally, the OrderedDict is converted to a tuple and assigned to an attribute. Now any class that uses OrderedClass as its metaclass:
will have a members attributes that gives the attributes in the order that they were defined:
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:
An Example – Final ClassesMany languages have the concept of a final class – one that cannot be inherited. Python does not have such a facility as any class can be inherited, but you can use a metaclass to make the equivalent of final. What is special about the metaclass mechanism is that it is the only Python construct that can come into effect before the class has been created. You can use decorators to modify a class after it has been created, but only a metaclass can intervene in the creation of a class. Even this doesn’t seem to be enough to stop a class being inherited as this is something that occurs after the class has been created, but it is not the original class that concerns us. When a class inherits from a class it also inherits the same metaclass. All we have to do is check that there isn’t a class in base that isn’t an instance of the metaclass. This has to be done before the class is created:
When you try:
and __new__ is called, the class is created because there isn’t anything in the base list. However, following this if you try:
then you will generate the exception because A is in Base and it is an instance of Final. In short, if any class in the base list is marked as final by using the metaclass Final then the new class isn’t created. Notice that many Python programmers would find this use of metaclasses not idiomatic Python because the feeling is that classes should always be inheritable and the most you should do is put a polite warning in the form of comments if you don’t want a class used in that way. Meta Attributes and MethodsFinal version in book How Classes become Functions – Metaclass __run__Final version in book Singleton RevisitedFinal version in book Abstract Base ClassFinal version in book Decorators RevisitedFinal version in book Using MetaclassIt is difficult to present any convincing use cases for metaclass, largely because many obvious uses can be implemented more directly using decorators. The key facts about metaclass is that it gives you a way of intervening in the class before the class object has been constructed. It also gives you a way of working with and organizing sets of classes. Think of a metaclass’s relationship to classes as being similar to the relationship between class and instance. This is not to say that you should underestimate what can be done with a metaclass. For example, the slowly developing gradual typing for Python makes a lot of use of metaclass and is an example of how you can create sophisticated systems using it. Summary
Programmer's Python
|
|||
Last Updated ( Monday, 06 August 2018 ) |