元类
编写自定义元类分为两个步骤:
编写元类类型的子类。
使用元类挂钩将新元类插入到类创建流程中。
我们使 type 类实现子类化,并修改魔术方法,比如 __init__、__new__、__prepare__ 以及 __call__,以便在创建类时修改类的行为。这些方法包含基类、类名、属性及其值等方面的信息。在 Python 2 中,元类挂钩是称为 __metaclass__ 的类中的静态字段。在 Python 3 中,您可以将元类指定为类的基类列表中的一个 metaclass 参数。
由于 CustomMetaClass 的 __init__ 方法中的 print 语句,这些属性将会自动打印。我们假设您在 Python 项目中有个令人讨厌的合作者,此人更喜欢使用 camelCase 来命名类属性和方法。您知道这样不好,该合作者应该使用 snake_case(毕竟,这是 Python!)。我们能否编写元类,将所有这些 camelCase 属性更改为 snake_case?
您可能想知道我们在这里为什么使用 __new__ 而不是 __init__。__new__ 实际上是创建实例的第一步。它负责返回类的新实例。而在另一方面,__init__ 则不会返回任何内容。它只负责在创建实例后对其进行初始化。请牢记一条简单的经验法则:当需要控制新实例的创建时使用 new,而在需要控制新实例的初始化时则使用 init。
在元类中实现 __init__ 并不常见,因为它不是那么强大 — 在实际调用 __init__ 之前,已经构造了类。您可以将其视为具有一个类装饰器,但不同点在于:在构建子类时会运行 __init__,而不会为子类调用类装饰器。
因为我们的任务包括创建新实例(防止这些 camelCase 属性潜入类中),所以覆盖自定义 SnakeCaseMetaClass 中的 __new__ 方法。我们来确认下它是否运行:
它已运行!现在,你已了解了如何在 Python 中编写和使用元类。我们再来探究一下这有何用途。
在 Python 中使用元类
您可以使用元类对属性、方法及其值执行不同的准则。前面例子(使用 snake_case)的类似示例包括:
值的域限制
隐式转换自定义类的值(您可能希望向用户隐藏编写类的所有这些复杂方面)
执行不同的命名约定和样式准则(比如,“每种方法都应有一个文档字符串”)
向类添加新的属性
在类定义本身中定义所有这种逻辑时使用元类,主要原因就是为了避免在整个代码库中出现重复代码。
元类的实际使用
因为在子类中会继承元类,所以元类解决了代码冗余(不要重复自己 — DRY)这一实际问题。 通常情况下,在生成类对象的同时,通过执行额外操作或添加额外代码,元类也可以帮助提取有关类创建的复杂逻辑。元类的一些实际用例包括:
抽象基类
类的注册
在库和框架中创建 API
我们来具体看一下每个示例。
抽象基类
抽象基类是只能被继承而不会被实例化的类。Python 具有以下内容:
我们来创建一个从 Vehicle 类继承的 Truck 类:
请注意,我们没有实现抽象方法。我们来看下如果尝试实例化 Truck 类的对象会发生什么情况:
可以通过在 Truck 类中定义两种抽象方法来修复这个问题:
学会了吗?
领取专属 10元无门槛券
私享最新 技术干货