Post

Python -- Metaclass

  • PEP 3119

How does metaclass work?

Read Python/bltinmodule.c#builtin___build_class__ You can see that the execute sequence is

  1. Class body code block runs, which populates the namespace ns of this class
  2. Run class’s meta class with ns to generate the class definition.
    1. It will call Meta class’s __new__, i.e., type_new function to generates the new class

Example

1
2
3
4
5
6
7
8
9
10
11
12
class MyMeta(type):
    def __new__(cls, name, bases, ns):
        x = super().__new__(cls, name, bases, ns)
        print("inside my meta", ns)
        return x

class A(metaclass=MyMeta):
    a = 100
    print("initialization a", a)

    def f1():
        print("inside f1")

Output of above code is

1
2
3
xiongding ~/00git/cpython $ ./python.exe ~/test.py
initialization a 100
inside my meta {'__module__': '__main__', '__qualname__': 'A', 'a': 100, 'f1': <function A.f1 at 0x1011a0940>}

How does AbstractMethod work?

abstractmethod decorator will add a flag indicating that this function is a abstract method. See below code.

1
2
3
def abstractmethod(funcobj):
   funcobj.__isabstractmethod__ = True
    return funcobj

Then, ABCMeta records all abstract methods. See below code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ABCMeta(type):
    def __new__(mcls, name, bases, namespace, /, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        # Compute set of abstract method names
        abstracts = {name
                     for name, value in namespace.items()
                     if getattr(value, "__isabstractmethod__", False)}
        for base in bases:
            for name in getattr(base, "__abstractmethods__", set()):
                value = getattr(cls, name, None)
                if getattr(value, "__isabstractmethod__", False):
                    abstracts.add(name)
        cls.__abstractmethods__ = frozenset(abstracts)
        ...

When you initialize a class and this class has non-empty abstract methods, then error is raise. See type_new code. When you subclass a abstract class and override the abstract methods, then see above in ABCMeta, namespace only contains the overrided method, so this subclass won’t be abstract.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
In [19]: from abc import ABCMeta, abstractmethod
    ...:
    ...: class M(ABCMeta):
    ...:     def __new__(cls, name, bases, ns):
    ...:         x = super().__new__(cls, name, bases, ns)
    ...:         print(ns)
    ...:         return x
    ...:
    ...:
    ...: class A(metaclass=M):
    ...:     @abstractmethod
    ...:     def fx(self):
    ...:         pass
    ...:
    ...: class B(A):
    ...:     def fx(self):
    ...:         print('fx')
    ...:
{'__module__': '__main__', '__qualname__': 'A', 'fx': <function A.fx at 0x102ebe790>}
{'__module__': '__main__', '__qualname__': 'B', 'fx': <function B.fx at 0x102ebe940>}

In [20]: A()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-6234893e030b> in <module>
----> 1 A()

TypeError: Can't instantiate abstract class A with abstract methods fx

In [21]: B()
Out[21]: <__main__.B at 0x102fe1460>

How does __init_subclass__ work?

It is subclass registration. See function typeobject.c#type_new_impl.

This post is licensed under CC BY 4.0 by the author.