Metaclasses in Python

Note: This is a draft of a book to be published. You can see it here. For more information, click [read original text] to view.

9.5.2 class is an instance of metaclass

What is "metaclass"? According to Confucius' saying "review the old and know the new", review the old first and follow the operation of "review the old", new problems will be found.

>>> type(7)
<class 'int'>

This is a familiar operation. int is the type of instance 7 and an integer class. It must be an object - everything is an object. In that case, it should also have types.

>>> type(int)
<class 'type'>

The type of class or type (object) int is type. According to the above ideas, you can also continue to execute type(type) -- type is also an object.

>>> type(type)
<class 'type'>

Class type is an object of type -- it seems awkward.

Look again:

>>> class Foo: pass
...
>>> f = Foo()
>>> type(f)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>
>>> type(type)
<class 'type'>

For instance F, the return value of type(f) has been explained earlier. Continue to trace the custom class Foo, which is also type, and the class type is also type. Or it is more convenient to summarize the above laws in the following ways:

>>> f.__class__
<class '__main__.Foo'>
>>> f.__class__.__class__
<class 'type'>
>>> f.__class__.__class__.__class__
<class 'type'>
>>> f.__class__.__class__.__class__.__class__
<class 'type'>

By incomplete induction, we can get:

  • An instance is an instance of a class;
  • Class is an instance of type class;
  • The type class is an instance of the type class.

It all comes down to type, which is the beginning. In Chinese, the word "Yuan" is often used to describe this feature. Philosophically, "Yuan" refers to the basis of world unity, the organizational cells of the world, and the specific existence and manifestation of the world. Then, for type, it can also be named with "meta", and type is still a class, so it is called Metaclass. Figure 9-5-1 shows the relationship between classes such as type and instances.

Figure 9-5-1 instance, class and metaclass

In the help document displayed by help(type), type can be used in three ways:

class type(object)
 |  type(object_or_name, bases, dict)
 |  type(object) -> the object's type
 |  type(name, bases, dict) -> a new type

The third type(name, bases, dict) can return a new type, that is, a new class - the type is the class.

  • Name: the name of the referenced new class;
  • bases: declare the parent class in the form of tuple;
  • dict: declare class properties in the form of a dictionary.
>>> Foo = type("Foo", (), {})

Create a class named Foo with type -- note that the object referenced by Foo is a class.

>>> Foo.__bases__
(<class 'object'>,)

Although the parameter bases is not assigned a value, all classes in Python take object as the base class, so viewing the parent class of class Foo will get the above results. But this class is actually "empty". It is the same as the previously defined class Foo: pass.

Then instantiate the class Foo. Of course, you can see that the instance f is of Foo type, and you can follow the previous example and keep looking until the metaclass type.

>>> f = Foo()
>>> f.__class__
<class '__main__.Foo'>
>>> f.__class__.__class__
<class 'type'>

If you assign values to the other two parameters of type, bases and dict, the generated class is not "empty".

>>> class Book:
...     def __new__(cls, *args, **kwargs):
...         cls.website = "www.itdiffer.com"
...         return super().__new__(cls)
...
>>> Bar = type('Bar', (Foo, Book), {'name':'learn python'})
>>> Bar.__bases__
(<class '__main__.Foo'>, <class '__main__.Book'>)
>>> Bar.__dict__
mappingproxy({'name': 'learn python', '__module__': '__main__', '__doc__': None, 'website': 'www.itdiffer.com'})

The class Bar created by using metaclass type inherits Foo and Book. In addition to the name set at the time of creation, there is also a website inherited from the Book class.

>>> b = Bar()
>>> b.name
'learn python'
>>> b.website
'www.itdiffer.com'
>>> b.__class__
<class '__main__.Bar'>
>>> b.__class__.__class__
<class 'type'>

After the class Bar is instantiated, you can also read the values of the name and website attributes of the instance, and the instance type is Bar.

It can be said that various types of classes required can be defined in the form of type(name, bases, dict). However, in practice, this form is not commonly used, but prefers to use the following methods:

>>> class Meta(type): pass
...

Class Meta inherits type, which is also called metaclass. The code block of the class in the above example only writes pass. If you have more complex needs, you can use the knowledge you have learned to write more statements in the class code block. The metaclasses defined in this way are more extensible and readable than the direct use of type(name, bases, dict).

If you create a new class with the metaclass Meta you have created now, use the following method:

>>> class Spam(metaclass=Meta): pass
...
>>> s = Spam()

The difference between the class Spam defined here and the previously defined classes is that the class name is followed by parentheses. In the parentheses, the metaclass parameter metaclass is used to describe the metaclass (note that this is not inheritance). In this way, the class Spam is created by using the metaclass Meta. The above process can also be described in the following language:

  • Class Spam is an instance of metaclass Meta;
  • The metaclass Meta instantiation obtains the class Spam.
>>> s.__class__
<class '__main__.Spam'>
>>> s.__class__.__class__
<class '__main__.Meta'>
>>> s.__class__.__class__.__class__
<class 'type'>

Let's enrich the content in the metaclass to make the defined metaclass have some special functions. You can write various properties and methods in metaclasses -- metaclasses are also classes, so the methods of previously defined classes are still valid here (the following code is written in IDE).

#coding:utf-8
'''
filename: metaclass.py
'''
class AuthorMeta(type):
    def __new__(cls, name, bases, attrs):
        print("AuthorMeta is called.")
        attrs['__author__'] = "laoqi" 
        return type.__new__(cls, name, bases, attrs)

The construction method described in section 9.4__ new__ () can be used not only in defining ordinary classes, but also in metaclasses. Its role is still to "construct" the structure of the class, such as attributes.

Then enter the interaction mode at the current position (see section 8.5.2 of Chapter 8), instantiate the metaclass just created, and get a "normal" class.

>>> from metaclass import *
>>> class Foo(metaclass = AuthorMeta): pass
... 
AuthorMeta is called.

As we know, the first step of instantiation is to call the constructor to generate an instance (see section 9.4.1 for details), which is also applicable to the process of metaclass instantiation to obtain a "normal" class - Foo is the instance of metaclass AuthorMeta. Therefore, the constructor in the metaclass AuthorMeta is executed immediately after the Foo class is defined__ new__ () .

Continue to edit the metaclass.py file in the IDE, and enter the following code:

class Python(metaclass=AuthorMeta):
    def __init__(self, bookname):
        self.bookname = bookname
        
    def author(self):
        return self.__author__

The following operations return to interactive mode. After reloading the module, perform the following operations (refer to section 8.5.2 of Chapter 8).

>>> from metaclass import *
AuthorMeta is called.
>>> Python.__author__
'laoqi'
>>> Python.__dict__
mappingproxy({'__module__': 'metaclass', '__init__': <function Python.__init__ at 0x7fcf3df3b700>, 'author': <function Python.author at 0x7fcf3df3b790>, '__author__': 'laoqi', '__dict__': <attribute '__dict__' of 'Python' objects>, '__weakref__': <attribute '__weakref__' of 'Python' objects>, '__doc__': None})

When executing from metaclass import *, the Python interpreter has compiled the metaclass.py file and generated the instance Python class from the metaclass AuthorMeta. In addition, the construction method of metaclass AuthorMeta__ new__ Properties defined in ()__ author__ It has become the class attribute of the instance Python class -- just what we want, the metaclass defines more general objects, and they should be "passed on from generation to generation".

Continue to instantiate the Python class, and the subsequent operations will be familiar.

>>> book = Python("learn python")
>>> book.bookname
'learn python'
>>> book.author()
'laoqi'
>>> book.__author__
'laoqi'

In this section, it is repeatedly emphasized that "a class is an instance of a metaclass". Based on this understanding, the following is defined in the metaclass__ call__ () method.

>>> class Meta(type):
...     def __call__(cls, *args, **kwargs):
...         print(f"cls in Meta.__call__ is  {cls}")
...         return type.__call__(cls, *args, **kwargs)
...
>>> class Painter(metaclass=Meta):
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...
>>> p = Painter('Dynami', 30)
cls in Meta.__call__ is  <class '__main__.Painter'>
>>> p.name
'Dynami'

Class Painter is an object. Adding parentheses after this name is execution, and the result of execution is the creation of an instance p.

Defined in the metaclass__ call__ () the method will be called only when the instance of the metaclass (Painter class) is executed, that is, the method in the metaclass Meta will be executed when the class Painter is instantiated__ call__ () .

Then observe the methods in the metaclass__ call__ (). The first parameter cls is the metaclass instance Painter. In section 9.4.1, the "normal" class is defined__ call__ () method, the first parameter is represented by self, which represents the instance of the current class. In the abstract, both represent instances -- Painter is an instance of Meta.

Once you understand the above, you can override it in a metaclass__ call__ () to implement the singleton (see section 9.4.2, where the construction method _new _() is not used).

>>> class SingletonMeta(type):
...     _instance = {}
...     def __call__(cls, *args, **kwargs):
...         if cls not in cls._instance:
...             cls._instance[cls] = type.__call__(cls, \
                                                   *args, \
                                                   **kwargs) # (1)
...         return cls._instance
...
>>> class Spam(metaclass=SingletonMeta): pass                # (2)
...
>>> x = Spam()
>>> y = Spam()
>>> x is y
>>> x
{<class '__main__.Spam'>: <__main__.Spam object at 0x7f86d1969f40>}
>>> y
{<class '__main__.Spam'>: <__main__.Spam object at 0x7f86d1969f40>}

When the class Spam is executed, that is, instantiated, the__ call__ () method. Annotation (1) implements class properties_ The assignment of instance takes the current instance object as the key, that is, the Spam object as the key, and the value is the value of class type__ call__ () method return value. After the cls class Spam is created in note (2), both x and y refer to the Spam object. Therefore, in fact, the same object is obtained, which implements the so-called singleton.

Because metaclasses are classes that define classes, if applied skillfully, they can make the code concise, compact and more elegant. It is very convenient to call some classes.

However, readers need not worry that not fully mastering metaclasses will affect their work. After all, in most cases, the required functions can be realized without the knowledge of this section. Therefore, it doesn't matter if you don't understand metaclasses at the moment. It's just your ticket to the top.

★ self study suggestions When defining a class, by overriding some special methods, you can make the defined class more "personalized", so as to design the class elegant and powerful. All special methods are used__< name>__ The naming format is double underlined before and after < name >, so it is also called "dunder method" (dunder: Double underscore) in English. In official documents( https://docs.python.org/3/reference/datamodel.html#special -Several special methods are listed in method names for the reference of readers interested in in in-depth research. "

Posted by waskelton4 on Thu, 04 Nov 2021 05:50:56 -0700