# 元类概述

# 元类介绍

首先明确一点,python一切皆对象,所有的对象都是实例化或者说是通过调用类而得到的,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类​。

元类就是用来实例化产生类的类,因此我们可以得到如下的关系:

可以通过type​​方法查看python内置的元类:

>>> class Test():
...     pass
...
>>> type(Test)  # 查看自定义类的类型
<class 'type'>
>>> type(int)  # 查看python内置类型的类型
<class 'type'>

上述代码结果都为<class 'type'>​​,type​​就是内置的元类,class关键字定义的所有的类以及内置的类都是由元类type实例化产生。

# type 和 object

由于在python3中没有经典类和新式类的区别,因此python3中object​是所有类的基类,那内置元类type​又是什么?

type​是object​类的类型,总结来说就是 type​**object的类型,同时,object又是type的基类** ,这句话看起来就有问题,到底是先有type​还是先有object​呢?这个问题有点类似于先有鸡还是先有蛋,但我们可以通过代码简单分析一下:

>>> object.__class__
<class 'type'>
>>> type.__class__
<class 'type'>

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

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

在python3中类和类型是同一种东西,因此对象.__class__​和type(对象)​得到的结果是一致的,object​的基类为空,但是type​的基类为object​,但是object​的类型又是type​。

具体也是挺复杂的,简单来说,object​和type​是python中的两个源对象,type​是所有类对象的类型,object​是所有类的基类。

# metaclass

meta-class 的 meta 这个词根,起源于希腊语词汇 meta,包含下面两种意思:

  • “Beyond”,例如技术词汇 metadata,意思是描述数据的超越数据;
  • “Change”,例如技术词汇 metamorphosis,意思是改变的形态。

metaclass 是 type 的子类,通过替换 type 的__call__运算符重载机制,“超越变形”正常的类。

metaclass,一如其名,实际上同时包含了“超越类”和“变形类”的含义,完全不是“基本类”的意思。所以,要深入理解 metaclass,我们就要围绕它的超越变形特性。

metaclass实际上拦截的是类的定义,其重写了type 的__call__运算符重载机制,当设置类的metaclass时,类便不再由type创建,而是调用metaclass的__call___​运算法重载。

from utils import debug
class MyMeta(type):

    @debug
    def __init__(cls, name, bases, attr_dict):
        return super().__init__(name, bases, attr_dict)

    @classmethod
    @debug
    def __prepare__(cls, name, bases):
        return {'x': 42}

    @debug
    def __new__(cls, name, bases, namespace):
        return super().__new__(cls, name, bases, namespace)

class MyClass(metaclass=MyMeta):
    def foo(self):
        pass


'''
Calling __prepare__
    with args: (<class '__main__.MyMeta'>, 'MyClass', ())
    kwargs: {}
    local variables: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000025A5B710FD0>, '__spec__': None, '__annotations__': {}, '__bu
iltins__': <module 'builtins' (built-in)>, '__file__': 'd:/Program/Workspace/CPythonTest/PyTests/metaclass.py', '__cached__': None, 'debug': <function debug at 0x0000025A5B79A040>, 'MyMeta': <class '__main__.MyMeta'>}

Calling __new__
    with args: (<class '__main__.MyMeta'>, 'MyClass', (), {'x': 42, '__module__': '__main__', '__qualname__': 'MyClass', 'foo': <function MyClass.foo at 0x0000025A5BA08670>})
    kwargs: {}
    local variables: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000025A5B710FD0>, '__spec__': None, '__annotations__': {}, '__bu
iltins__': <module 'builtins' (built-in)>, '__file__': 'd:/Program/Workspace/CPythonTest/PyTests/metaclass.py', '__cached__': None, 'debug': <function debug at 0x0000025A5B79A040>, 'MyMeta': <class '__main__.MyMeta'>}

Calling __init__
    with args: (<class '__main__.MyClass'>, 'MyClass', (), {'x': 42, '__module__': '__main__', '__qualname__': 'MyClass', 'foo': <function MyClass.foo at 0x0000025A5BA08670>})
    kwargs: {}
    local variables: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000025A5B710FD0>, '__spec__': None, '__annotations__': {}, '__bu
iltins__': <module 'builtins' (built-in)>, '__file__': 'd:/Program/Workspace/CPythonTest/PyTests/metaclass.py', '__cached__': None, 'debug': <function debug at 0x0000025A5B79A040>, 'MyMeta': <class '__main__.MyMeta'>}

'''

# class关键字创建类的步骤

class关键创建类时,一定调用了元类,调用元类type​又需要类的三大组成部分,分别是:

  1. 类名,比如class_name = 'Test'
  2. 类的父类(基类),比如class_bases = (object, )
  3. 类的名称空间class_dict,类的名称空间是执行类体代码时得到的

在调用type时会依次传入以上三个参数,此处我们借用exec​分析class关键字借助type​元类产生类的步骤:

# 1 定义类名
class_name = 'Test'

# 2 定义类的基类(父类)
class_bases = (object,)

# 3 执行类体代码拿到类的名称空间
class_dic = {}

# 4 定义类体代码(本质是字符串)
class_body = """
def __init__(self,name,age):
    self.name=name
    self.age=age

def test(self):
    print('%s:%s' %(self.name,self.name))
"""

# 5 将字符串转为python能识别的语法:将class_body运行时产生的名字存入class_dic中
exec(class_body, {}, class_dic)
# 查看类的名称空间
print(class_dic)
# {'__init__': <function __init__ at 0x000001DE1166F0D0>, 'test': <function test at 0x000001DE11DD1310>}

# 6 调用元类产生类
Test = type(class_name, class_bases, class_dic)
print(Test)
# <class '__main__.Test'>

# 7 调用类产生对象
t = Test('python', '12')
print(t)
# <__main__.Test object at 0x000001DE11B42E20>

#__new__​​和__init__控制类的创建

#__new__​和__init__

可以通过控制调用元类的步骤控制元类的产生,元类的调用与自定义的普通类的调用步骤相同.

# 定义一个类
class Test():

    def __init__(self):
        self.name = 'python'

    def test(self):
        print(self.name)


# 类实例化产生对象
t = Test()

基于之前的知识,我们知道类实例化产生对象时,会自动调用类中的__init__​方法,为空对象增加独有属性,问题来了,这个空对象是从哪来的呢?所以说,在调用__init__​方法之前肯定还调用了其他方法。

在python3中没有经典类和新式类之分,python3中自定义的普通类都默认继承了object​类,所以在调用类产生对象时,在__init__​方法运行之前的方法生成了一个空对象并返回,将这个空对象作为参数传给__init__​方法,这个方法就是__new__​方法。

在类加括号实例化对象时,首先会执行__new__​方法,该方法早于__init__​方法运行,返回一个空对象,如果该方法没有返回值,就不会调用__init__​方法。

知道了类调用的步骤,就可以自定义元类来控制类的产生了。

# 自定义元类

一个类没有指定自己的元类,默认这个类的元类就是type​,除了使用内置元类type​,我们可以通过继承type​类从而自定义元类,通过在创建普通类时将metaclass​关键字指定为自定义元类的名字来为普通类指定元类。

# 自定义元类
class MyMeta(type):  # 自定义元类必须继承type,否则就是普通的类

    '''
    早于__init__方法执行,必须返回空对象,由于该方法是调用类后第一个运行的方法,此时并没有对象产生,
    因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs用来接收调用元类产生对象所需的参数(类名 类的基类 名称空间)
    '''
    def __new__(cls, * args, ** kwargs):
        return type.__new__(cls, *args, **kwargs)  # 直接调用父类type中的__new__方法

    '''
    通过__init__控制类的产生,调用自定义元类与调用内置元类type的方式相同,需要传入类名、类的父类们、类体代码的名称空间
    __init__方法中第一个参数self来自于__new__方法产生的空对象。
    '''
    def __init__(self, class_name, class_bases, class_dict):
        '''
        在该方法内可以控制类的产生
        '''
        if not class_name.istitle():  # 实现类名首字母必须大写,否则抛出异常
            raise NameError('类名的首字母必须大写')

        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip()) == 0:  # 实现类必须有文档注释,否则抛出异常
            raise TypeError('必须有文档注释')

# 使用元类

首先,不使用class关键字创建类请参照class关键字创建类的原理;

然后,来看一下使用自定义元类控制class关键字创建类,大家可以尝试不加文档注释以及类名首字母不大写来验证元类如何控制类的产生:

class Test(metaclass=MyMeta):
  
    '''
    我是文档注释
    '''
  
    def __init__(self):
        self.name = 'python'
  
    def test(self):
        print('test')

从上述代码我们可以得到如下结论:

  • 只要调用类,就会依次调用类中的__new__​方法和__init__​方法;
  • __new__​方法返回一个空对象,就类似一张白纸;
  • __init__​获取__new__​方法中产生的白纸​在上面画不同的图案。
  • 到底是应该重新定义__init__​还是__new__​,这取决于我们是想修改类的定义还是操纵完成成型的类对象。

#__call__​​控制类的调用

为什么类加括号就可以被调用呢?类调用之后又是如何保证先运行类中的__new__​方法再运行类中的__init__​方法的?

上述问题的答案就是类中定义的__call__​方法,如果想让一个对象变成一个可调用对象(加括号可以调用),需要在该对象的类中定义__call__​方法,调用可调用对象的返回值就是__call__​方法的返回值。

class Test():
  
    def __init__(self):
        self.name = 'python'
  
    def __call__(self, *args, **kwargs):  # self是Test类的对象
        print(self)  # <__main__.Test object at 0x000001C78CE78FD0>
        print(self.name)
  
t = Test()
t()  # python

因此我们可以得到以下结论:

对象加括号调用会调用该对象的类中定义的__call__​​方法;
类加括号调用会调用内置元类或自定义元类中的__call__​​方法,取决于类的元类是什么;
自定义元类加括号调用会内置元类中的__call__​​方法。

我们可以简单验证一下上述结论:

class MyMeta(type):

    def __call__(self, *args, **kwargs): 
        print(self)
        print(args)
        print(kwargs)
  
        return 'test'

class Test(metaclass=MyMeta):
  
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 调用Test就调用了
t = Test()
print(t)

'''
<class '__main__.Test'>
('haha', '123')
{}
test
'''

通过上述代码我们可以推断出调用Test​时,会调用自定义元类中的__call__​方法,并将返回值赋值给调用类产生的对象。

我们也可以通过__call__​方法自定义元类来控制类的调用,也就是产生对象。

class Mymeta(type):

    def __init__(self, class_name, class_bases, class_dict):

        # 实现类名首字母必须大写,否则抛出异常
        if not class_name.istitle():
            raise NameError('类名的首字母必须大写')
        # 实现创建的类必须有文档注释,否则抛出异常
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip()) == 0:
            raise TypeError('必须有文档注释')

    def __new__(cls, *args, **kwargs):

        return type.__new__(cls, *args, **kwargs)

    def __call__(self, *args, **kwargs):
        # self指的就是当前类产生的对象
        people_obj = self.__new__(self)
        self.__init__(people_obj, *args, **kwargs)
        return people_obj


Test = MyMeta(class_name, class_bases, class_dic)  # 调用的是内置元类type的__call__方法


class Test(metaclass=Mymeta):
    """
    我是文档注释
    """

    def __new__(cls, *args, **kwargs):
        # 产生空对象--真正的对象,真正造对象的是object
        return object.__new__(cls)  # 这里使用type也没有问题

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


t = Test()  # 调用的是自定义元类中的__call__方法

通过自定义元类来创建类的时候,会调用type​的__call__​方法,该方法内部会做三件事情:

type.__call__​做的事情:
1、先调用自定义元类中的__new__​方法,产生一个空对象
2、在调用自定义元类中的__init__​方法,为空对象添加独有属性
3、返回一个初始化好的自定义元类的对象,就是上述的Test类

调用Test​类时则会调用自定义元类MyMeta.__call__​方法,同样也会做三件事:

MyMeta.__call__​做的事情:
1、先调用 Test​类中的__new__​方法产生一个空对象
2、再调用Test​ 类中的__init__​方法为空对象添加独有属性
3、返回一个初始化好的Test​类的对象赋值给t

# __prepare__​初始化类命名空间

__prepare__​是一个特殊方法,用于在创建类对象之前为类定义命名空间做准备工作。在类定义中,如果定义了__prepare__​方法,那么Python会调用这个方法来创建类的命名空间,并将该命名空间传递给__new__​方法。因此,通过重写__prepare__​方法,可以自定义类的命名空间,并可以控制该命名空间中的属性和方法的顺序。

例如有如下代码,OrderedClass​类定义了一个__prepare__​方法来返回一个有序字典作为命名空间。然后,__new__​方法将该字典中的属性和方法按照定义的顺序重新排列,并将它们存储在members​元组中。最后,MyClass​类使用了OrderedClass​类作为元类,并定义了两个属性和一个方法。当我们实例化一个MyClass​对象时,可以看到该类的members​元组是按照定义的顺序排列的。

能够获取到类熟悉的定义顺序看起来微不足道,但对于类似ORM这些的特定类型的应用来说却是非常重要的功能。

class OrderedClass(type):
    def __new__(meta, name, bases, dct):
        # 重载new方法以便重新排列属性和方法
        # 然后使用常规方法创建类
        result = type.__new__(meta, name, bases, dct)
        result.members = tuple(dct.keys())
        return result

    @classmethod
    def __prepare__(meta, name, bases):
        # 返回一个有序字典
        return collections.OrderedDict()

class MyClass(metaclass=OrderedClass):
    a = 1
    b = 2

    def __init__(self):
        pass