Python基础 - 元类

元类

Posted by 王富杰 on Thursday, May 22, 2025

一、元类

在自定义类的过程中,通过class关键字来创造一个类,那class是如何把类造出来的呢,那就是通过元类。元类是一个用于实例化产生类的类。可以使用type函数查看类的元类

class Human:
    pass
print(type(Human))

## 执行结果
<class 'type'>

可以看到,type类就是Human的元类。因此用class关键字定义的类以及内置的类,都是调用内置的元类type实例化产生的。

1.1、class分析

我们来看一下关键字class定义一个类,内部做了哪些动作。既然类是调用元类type()产生的,那首先要给元类传参,这里需要准备参数, 例如我们定义一个Human类

#  类名
class_name = 'Human'
# 基类, 默认继承pbject类
class_bases = (object,)

# 执行类子代码,产生名称空间
class_dic = {}
class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age
def f1(self):
    print(self.name, self.age)
'''
exec(class_body, {}, class_dic)  # 中间一个字典传入对全局变量的引用,如果没有传入空字典

如上可以看到,我们需要准备类名,类继承的父类,类的子代码作为字符串传入生成类的名称空间。接下来就可以调用元类了:

print(type(class_name, class_bases, class_dic))
Human = type(class_name, class_bases, class_dic)

## 执行结果
<class '__main__.Human'>

如上所示,我们就自己通过调用type元类创造了一个类,没有通过class关键字。

1.2、自定义元类

前面我们通过调用type元类创造了新类,也可以自定义一个元类,就可以使用高度定制化新类。自定义的元类需要继承type。

class MyType(type):
    def __init__(self, class_name, class_bases, class_dic):
        if '_' in class_name:
            raise NameError('类名不允许有下划线')

        if not class_dic.get('__doc__'):
            raise SyntaxError('定义类必须写注释')
        print(class_bases)
        print(self.__bases__)

    def __new__(cls, *args, **kwargs):
        print('Mytype.__new__')
        print(cls)
        print(args)
        print(kwargs)
        return super().__new__(cls, *args, **kwargs)

class Human(metaclass=MyType):
    """
    测试元类
    """
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def f1(self):
        print(self.name, self.age)

如上我们就自定义了一个元类,并使用自定义元类创造了Human类,在元类中,我们做了类型不能带下划线,类必须有文档等约束。这里说明一下造Human类的三个步骤

  • 调用Mytype的__new__方法 产生一个空对象Human
  • 调用MyType的init方法,初始化对象Human
  • 返回初始化好的对象 Human

使用元类造类的第一步,就是调用了__new__方法,__new__方法帮类继承了object,并创建一个空对象进行返回。

这三个步骤在底层调用了 __call_方法完成了这三步,我们这里研究一下__call_方法

class Test:
    def __call__(self, *args, **kwargs):
        print("__call__")
obj = Test()
obj()

如上,在一个类中定义了__call_方法, 当对象后加括号时,就自动调用了这个方法。因此我们在使用MyType()造类时,调用了它的父类也就是type类的__call_方法。因为前边我们说了使用class定义类的第四步就是调用元类。

二、属性查找

如果通过对象找属性,还是和以前一样,一直找到object类,没有就会报错,但是通过类找属性时,object类没有,还会去元类里面找。这里就不再进行演示了。最后需要说明,一般编程中,基本不会自定义元类,关于元类的知识这里可以只做了解。

「真诚赞赏,手留余香」

WangFuJie Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付