返回介绍

21.6 元类的特殊方法 __prepare__

发布于 2024-02-05 21:59:46 字数 2199 浏览 0 评论 0 收藏 0

在某些应用中,可能需要知道类的属性定义的顺序。例如,对读写 CSV 文件的库来说,用户定义的类可能想把类中按顺序声明的字段与 CSV 文件中各列的顺序对应起来。

如前所述,type 构造方法及元类的 __new__ 和 __init__ 方法都会收到要计算的类的定义体,形式是名称到属性的映像。然而在默认情况下,那个映射是字典;也就是说,元类或类装饰器获得映射时,属性在类定义体中的顺序已经丢失了。

这个问题的解决办法是,使用 Python 3 引入的特殊方法 __prepare__。这个特殊方法只在元类中有用,而且必须声明为类方法(即,要使用 @classmethod 装饰器定义)。解释器调用元类的 __new__ 方法之前会先调用 __prepare__ 方法,使用类定义体中的属性创建映射。__prepare__ 方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成的元组,返回值必须是映射。元类构建新类时,__prepare__ 方法返回的映射会传给 __new__ 方法的最后一个参数,然后再传给 __init__ 方法。

理论听起来很复杂,但是我见过的 __prepare__ 方法都十分简单。请看示例 21-16。

示例 21-16  model_v8.py:这一版 EntityMeta 元类用到了 __prepare__ 方法,而且为 Entity 类定义了 field_names 类方法

class EntityMeta(type):
  """元类,用于创建带有验证字段的业务实体"""

  @classmethod
  def __prepare__(cls, name, bases):
    return collections.OrderedDict()  ➊

  def __init__(cls, name, bases, attr_dict):
    super().__init__(name, bases, attr_dict)
    cls._field_names = []  ➋
    for key, attr in attr_dict.items():  ➌
      if isinstance(attr, Validated):
        type_name = type(attr).__name__
        attr.storage_name = '_{}#{}'.format(type_name, key)
        cls._field_names.append(key)  ➍


class Entity(metaclass=EntityMeta):
  """带有验证字段的业务实体"""

  @classmethod
  def field_names(cls):  ➎
    for name in cls._field_names:
      yield name

❶ 返回一个空的 OrderedDict 实例,类属性将存储在里面。

❷ 在要构建的类中创建一个 _field_names 属性。

❸ 这一行与前一版相比没有变化,不过这里的 attr_dict 是那个 OrderedDict 对象,由解释器在调用 __init__ 方法之前调用 __prepare__ 方法时获得。因此,这个 for 循环会按照添加属性的顺序迭代属性。

❹ 把找到的各个 Validated 字段添加到 _field_names 属性中。

❺ field_names 类方法的作用简单:按照添加字段的顺序产出字段的名称。

像示例 21-16 那样添加一些简单的代码之后,我们可以使用 field_names 类方法迭代任何 Entity 子类的 Validated 字段。示例 21-17 演示了这个新功能。

示例 21-17 bulkfood_v8.py:展示 field_names 用法的 doctest——无需修改 LineItem 类,field_names 方法继承自 model.Entity 类

>>> for name in LineItem.field_names():
...   print(name)
...
description
weight
price

对元类的介绍到此结束。在现实世界中,框架和库会使用元类协助程序员执行很多任务,例如:

验证属性

一次把装饰器依附到多个方法上

序列化对象或转换数据

对象关系映射

基于对象的持久存储

动态转换使用其他语言编写的类结构

下一节将概述 Python 数据模型为所有类定义的方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文