What the heck is a metaclass?

This article has participated in the "Newcomer Creation Ceremony" activity, and started the road of Nuggets creation together

foreword

Recently, I was looking at object-oriented programming in Python, and I got stuck at the knowledge point of metaclasses. After searching and learning various data, I came up with this article. Again, the ability and time are limited. If there are mistakes, I hope to criticize and correct them. ,thanks.

metaclass concept

In fact, the concept of metaclass is very simple.

The class that generates the class is the metaclass.

We all know that objects are instantiated by classes, as follows.

class Foo:
    pass


foo = Foo()
print(foo)

<__main__.Foo object at 0x7fd9280ef250>
复制代码

Then we think about who produced the next class. At this time, we will use the type function to take a look.

class Foo:
    pass


foo = Foo()
print(type(foo))
print(type(Foo))

<class '__main__.Foo'>
<class 'type'>
复制代码

We found that the type of the Foo class is type, let's see who generated the data type that comes with python.

print(type(int))
print(type(str))

<class 'type'>
<class 'type'>
复制代码

We can find that it is also a type, so type is a metaclass. The type metaclass instantiates the class (class object), and the class instantiates the object, so everything in Python is an object, whether it is a user-defined class or a class that comes with Python, it is instantiated by type.

typeCreate class

We usually define classes using the class keyword.

class Foo:

    i = 1

    def test(self):
        print('test')
复制代码

The metaclass is an instantiated class, so the type metaclass should be able to instantiate the class directly. Its syntax is:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
复制代码
i = 1


def test(self):
    print('test')


type('Foo', (), {'i': i, 'test': test})
复制代码

custom metaclass

We can also customize the metaclass, all we need to do is to inherit the type class in the custom metaclass, and then specify the custom metaclass with metaclass in the generated class.

class MyType(type):
    pass

class Foo(metaclass=MyType):
    pass

print(type(Foo))

<class '__main__.MyType'>
复制代码

The role of the custom metaclass is to implement some functions and apply it to the instantiated class, so that the instantiated classes have the same function. Before formally writing a custom metaclass, it is necessary to explain the __new__, __init__ and __call__ magic methods. Only by understanding and using these methods can the custom metaclass achieve the functions we want.

__new__ magic method

When introducing these magic methods, I will explain them from two different perspectives: ordinary classes and custom metaclasses.

First of all in a normal class, the new magic method is a constructor, which is used to instantiate the object.

class Foo:

    a = 1

    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        print(object.__new__(cls))
        return object.__new__(cls)

    def test(self):
        print('test')


foo = Foo()

<class '__main__.Foo'>
()
{}
<__main__.Foo object at 0x7f96380db160>
复制代码
  • Triggered when: when the object is instantiated
  • Role: instantiate an object
  • Parameters: cls is the current class, args, kwargs are the initialization parameters.
  • Return value: the instantiated object

In a custom metaclass, the new magic method is used to construct a class (class object), and its parameters are the same as those of the type function to construct a class.

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        print(mcs)
        print(name)
        print(bases)
        print(dicts)
        print(super().__new__(mcs, name, bases, dicts))
        return super().__new__(mcs, name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def test(self):
        print('test')


foo = Foo()

<class '__main__.MyType'>
Foo
()
{'__module__': '__main__', '__qualname__': 'Foo', 'a': 1, '__new__': <function Foo.__new__ at 0x7fca60176790>, 'test': <function Foo.test at 0x7fca60176820>}
<class '__main__.Foo'>
复制代码
  • Triggered when: when the class is instantiated.
  • Role: instantiate the class.
  • Parameters: mcs is the current metaclass, name is the instantiated class name, bases is the inherited class name of the instantiated class, and dicts is the attributes and methods of the instantiated class.
  • Return value: The instantiated class.

通过new魔术方法其实就可以实现很多自定义的功能了,例如我想让实例化的类的属性都变成大写。

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        attrs = ((name, value) for name, value in dicts.items() if not name.startswith('__'))
        dicts = dict((name.upper(), value) for name, value in attrs)
        return super().__new__(mcs, name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def test(self):
        print('test')


print(Foo.__dict__)

{'A': 1, 'TEST': <function Foo.test at 0x7fb0580b6820>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
复制代码
__init__魔术方法

在普通类中,就是初始化方法,self为类实例化的对象,这个很好理解,毕竟这个函数我们接触的很多。

class Foo:

    a = 1

    def __init__(self, name):
        self.name = name

    def test(self):
        print('test')


foo = Foo('li')
print(foo.name)

li
复制代码

在自定义元类中,那就是初始化类对象。

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        return super().__new__(mcs, name, bases, dicts)

    def __init__(cls, name, bases, dicts):
        print(cls)
        super().__init__(name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def __init__(self, name):
        self.name = name

    def test(self):
        print('test')

<class '__main__.Foo'>
复制代码
__call__魔术方法

在普通类中,call方法会在实例化对象加括号时触发。

class Foo:

    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)


foo = Foo('li')
foo()

<__main__.Foo object at 0x7fbd2806f250>
()
{}
复制代码

在自定义元类中,我们知道类是由元类生成,那类加括号就会触发call方法。

Foo = MyType()
Foo() 相当于 MyType()()
复制代码
class MyType(type):

    def __new__(mcs, name, bases, dicts):
        return super().__new__(mcs, name, bases, dicts)

    def __init__(cls, name, bases, dicts):
        print(cls)
        super().__init__(name, bases, dicts)

    def __call__(cls, *args, **kwargs):
        # obj = cls.__new__(cls)
        # cls.__init__(obj, *args, **kwargs)
        return type.__call__(cls, *args, **kwargs)



class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        print('foo new')
        return object.__new__(cls)

    def __init__(self, name):
        print('foo init')
        self.name = name

    def test(self):
        print('test')


foo = Foo('li')
print(foo.__dict__)

<class '__main__.Foo'>
foo new
foo init
{'name': 'li'}
复制代码

我们可以看出,调用type的call函数,其实和手动调动类的new和init方法是一样的结果。

单例模式

最后,我们来用元类实现单例模式。单例模式就是实例化的对象只有一个,简单的说,就是如果被实例化过了就返回该实例,这样就只会有一个实例。

class MyType(type):

    def __init__(cls, name, bases, dicts):
        print('init')
        cls.__instance = None
        super().__init__(name, bases, dicts)

    def __call__(cls, *args, **kwargs):
        print('call')
        if cls.__instance is None:
            cls.__instance = type.__call__(cls, *args, **kwargs)
        return cls.__instance


class Foo(metaclass=MyType):
    pass


foo1 = Foo()
foo2 = Foo()
print(id(foo1), id(foo2))

init
call
call
140402884588256 140402884588256
复制代码

今天的分享就到这了,我们下期再见~

Guess you like

Origin juejin.im/post/7079362278436372487