36、元类

面向对象 / 2021-01-05

在汉语中,元有本源、根本的意思!Python中,元类即可以通过实例化产生类的类,通俗来讲它就是类的祖师爷!Python内置的元类是type,我们用class关键字定义的类都是type实例化后得到的对象!因为Python一切皆对象,所以类本身也是对象,也可以通过实例化产生!

我们用class定义类的时候,Python都帮我们干了些什么呢?下面通过一步步的分析重现底层操作!

一、exec补充

exec函数能够执行储存在字符串或文件中的Python语句,它会将执行期间产生的名字存放于局部名称空间中,语法如下:

exec = (__source, __globals, __locals)

1.1、参数一:source

包含有一系列Python代码的字符串

1.2、参数二:globals

全局作用域(字典形式),如果不指定,默认为globals()

1.3、参数三:locals

局部作用域(字典形式),如果不指定,默认为locals()

二、class定义类的步骤

>>> class Chinese:
...     def __init__(self, name):
...         self.name = name
...
>>> gd = Chinese('sanxi')
2.1、定义类名

譬如:class_name='Chinese'

2.2、继承基类

譬如:class_bases=object,不指定则Python默认继承object!

2.3、开辟名称空间
  • 类名名称空间:class_dict = {}

  • 类体代码:class_body = '' def __init__(self, name):
    ... self.name = name''

  • 执行代码得到名称空间:class_dict = exec(class_body, {}, class_dict)

2.4、调用元类
Chinese = type(class_name, class_bases, class_dict)

三、自定义元类

Python中,不指定则所有类默认的元类是type!我们可以通过继承type来自定义元类,然后使用metaclass来为一个类指定元类!

3.1、调用自定义元类步骤:
3.1.1、调用__new__造空对象

object下默认有一个new方法,我们可使用它来创造空对象而不用特意去定义new方法!还记得之前说过Python3的类默认都继承object吗?按照名称查找顺序,类内没有会一级一级往上找,直至找到object的new方法!

3.1.2、调用Mymeta的init初始化对象

下面这个super其实是type的init方法,因为new方法先于init执行,因此也包含了new!

super(Mymeta, cls).__init__(class_name, class_bases, class_namesapce)
3.1.3、返回初始化好的对象
def __call__(cls, *args, **kwargs):  # 因为类是可以实例化产生对象的,所以必须是可调用的;__call__方法就是使对象变得可调用
    if args or kwargs:  # 有值时
        obj = object.__new__(cls)  # 造空对象
        cls.__init__(obj, args, kwargs)  # 初始化空对象
        return obj  # 返回对象
3.2、完整示例:
class Mymeta(type):  # 继承了type的类才能称之为自定义元类
    def __init__(cls, class_name, class_bases, class_namesapce):  # init控制普通类Chinese的创建
        # self是 <class '__main__.Chinese'>
        # class_bases是 (<class 'object'>,)
        # class_dic是Chinese的名称空间
        super(Mymeta, cls).__init__(class_name, class_bases, class_namesapce)  # 重用父类功能
        if class_name.islower():  # 自定义功能
            raise TypeError(f'类名:{class_name} 必须为驼峰体')
        if '__doc__' not in class_namesapce or len(class_namesapce['__doc__'].strip(' \n')) == 0:
            raise TypeError(f'类:{class_name} 必须包含注释且不能为空')
            
    def __call__(cls, *args, **kwargs):  # 控制类Chinese的调用过程
        if args or kwargs:
            obj = object.__new__(cls)
            cls.__init__(obj, args, kwargs)
            return obj


class Chinses(object, metaclass=Mymeta):  # 新类指定Mymeta为元类
    """
    注释信息
    """
    country = 'China'

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


gd = Chinses('sanxi', 18)
print(type(Chinses))

# 执行得到结果
<class '__main__.Mymeta'>
世间微尘里 独爱茶酒中