Python type 和元类 metaclass

date
Aug 25, 2022
slug
python-type-metaclass
status
Published
tags
Python
summary
Python 的 type 和 metaclass 之间的联系
type
Post

Python 一切皆对象,包括类 class 也是对象

众所周知 Python 跟 Java 一样,一切皆对象,所以 class 类也是对象,而对象可以动态创建,所以一个 class 类也可以被动态创建出来。
 

通过 type() 创建类

type 的定义,type 是一个类 class,只不过可以 __callable__ 。
type 传入一个参数时,返回的是参数的类型。
 
type 传入三个参数时,用来创建类:
第一个参数 name 是被创建的类的名字,str 类型
第二个参数 bases 是被创建的类的父类,tuple 类型,不传默认是 (object,)
第三个参数 dict 是被创建的类的属性和方法,dict 类型
下面两种创建类的方式,结果是一样的
 

通过 metaclass 创建类

我们知道可以用 class 类来创建 object 对象,而 class 类本身也是对象,那么 class 这个对象由谁来创建呢?答案就是 metaclass,metaclass 是 class 的 class,metaclass 创建 class,class 创建 object,metaclass→class→object:
a 是对象,对象的类型是 class,class 的类型是 metaclass
能创建类的类,就是 metaclass 元类,上述的 type 就是一个元类。
 
Python 2 中给一个 class 指定一个创建它的元类:
Python 3 中语法有变化:
 

metaclass 的存在的意义是动态创建类、拦截类、修改类

就像动态创建对象一样,你想在哪创建一个对象都可以,同样的你想创建一个自定义的类,然后根据它创建实例。
假设有一个需求是将左右的类的属性,都处理成大写的,演进过程如下:
给 module 指定一个 __metaclass__ 处理方法
自定义一个元类,注意元类是用来创建类的,所以必须继承自type
简写一下:
注意到最后一行,调用的是 type,这还不算 OOP,因为并没有调用父类,所以最好改成:
或者可以改成使用 super:
 

分层思想的体现

metaclass 可以做的事:
  • intercept a class creation
  • modify the class
  • return the modified class
应用层想用类创建实例,然后使用实例。而至于类是怎么来的,应用层并不关心,创建类这一步就交给元类处理,而在元类这一层中做修改,对上层应用来说是透明的。
 

metaclass 实际应用场景

最多的是用在定义 API 方面,这个 API 不是狭义的应用接口,而是更广泛意义的接口、协议,类似一种转换器的概念,API 给应用提供了简单的使用接口,而把复杂的处理转换隐藏在 metaclass 内部,经过处理的结果最终输出到另一个系统中。典型的例子就是 Django 中的 ORM:
当打印 person.age 时,并不是返回 IntegerField 对象,而是返回一个 int 值,而且还可以使用这个对象写入到 db 中,这一些都是因为 models.Model 背后的 metaclass,其讲复杂的类型转换等操作隐藏在内部,而给业务提供了一个十分简洁的使用接口。
另外就是需要动态生成类的地方,例如写一个 CacheMeta,可以给各种未知的类加缓存,具体给哪些类加缓存,对于这个元类来说是未知的,所以需要在运行过程中动态生成,此时就可以使用元类的方式。
 

别忘了修改类其实还可以使用装饰器

装饰器的思路也是分层,在类使用之前,给类套一层外壳。
 

type 是自己的对象,也是自己的元类

Python 一切皆对象,要么是 class 的对象,要么是 metaclass 的对象,而只有 type 例外。
type 是 type 自己的对象,type 也是 type 自己的 metaclass,这没办法在纯 Python 中实现,而是在语言实现层面做的特殊处理实现的。
 
 

type.__new__ type.__init__ 和 type.__call__ 关系

  • type.__new__:
    • 是在元类中,根据入参,创建出普通类,即从类语法定义生成类实体
    • metaclass 调用 __new__ 创建 class,就像 class 调用 __new__ 创建 object 一样
  • type.__init__:
    • 在 __new__ 完成后,即根据语法定义创建出类之后调用,给该类做初始化操作
    • 不产生什么返回值
    • metaclass 调用 __init__ 初始化 class,就像 class 调用 __init__ 初始化object一样
  • type.__call__:
    • 根据一个实体存在的类,创建一个该类的对象
    • 在一个具体对象 object 上直接调用 对象名(),会执行 class 中定义的 __call__,同理在 class 上直接调用 class(),会执行 metaclass 中定义的 __call__
 
参考:
 

© 菜皮 2020 - 2024