>>> from env_helper import info; info()
页面更新时间: 2024-03-29 16:20:49
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

1.21. 类元编程

(元类)是深奥的知识,99% 的用户都无需关注。如果你想知道是否需要使用元类,我告诉你,不需要(真正需要使用元类的人确信他们需要,无需解释原因)。 ——Tim Peters, Timsort 算法的发明者,活跃的 Python 贡献者

元类功能强大,但是难以掌握。使用元类可以创建具有某种特质的全新类中,例如我们见过的抽象基类。

本章还会谈及导入时和运行时的区别。

注:除非开发框架,否则不要编写元类。

1.21.1. 类工厂函数

collections.namedtuple 一样,类工厂函数的返回值是一个类,这个类的特性(如属性名等)可能由函数参数提供。
可以参见官方示例
原理:使用 type 构造方法,可以构造一个新的类。
>>> help(type)
Help on class type in module builtins:

class type(object)
 |  type(object) -> the object's type
 |  type(name, bases, dict, **kwds) -> a new type
 |
 |  Methods defined here:
 |
 |  __call__(self, /, *args, **kwargs)
 |      Call self as a function.
 |
 |  __delattr__(self, name, /)
 |      Implement delattr(self, name).
 |
 |  __dir__(self, /)
 |      Specialized __dir__ implementation for types.
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  __instancecheck__(self, instance, /)
 |      Check if an object is an instance.
 |
 |  __or__(self, value, /)
 |      Return self|value.
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __ror__(self, value, /)
 |      Return value|self.
 |
 |  __setattr__(self, name, value, /)
 |      Implement setattr(self, name, value).
 |
 |  __sizeof__(self, /)
 |      Return memory consumption of the type object.
 |
 |  __subclasscheck__(self, subclass, /)
 |      Check if a class is a subclass.
 |
 |  __subclasses__(self, /)
 |      Return a list of immediate subclasses.
 |
 |  mro(self, /)
 |      Return a type's method resolution order.
 |
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |
 |  __prepare__(...)
 |      __prepare__() -> dict
 |      used to create the namespace for the class statement
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs)
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __abstractmethods__
 |
 |  __annotations__
 |
 |  __dict__
 |
 |  __text_signature__
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  __base__ = <class 'object'>
 |      The base class of the class hierarchy.
 |
 |      When called, it accepts no arguments and returns a new featureless
 |      instance that has no instance attributes and cannot be given any.
 |
 |
 |  __bases__ = (<class 'object'>,)
 |
 |  __basicsize__ = 904
 |
 |  __dictoffset__ = 264
 |
 |  __flags__ = 2148031744
 |
 |  __itemsize__ = 40
 |
 |  __mro__ = (<class 'type'>, <class 'object'>)
 |
 |  __weakrefoffset__ = 368

1.21.2. 元类

元类是一个类,但因为它继承自 type,所以可以通过它生成一个类。

在使用元类时,将会调用元类的 __new____init__,为类添加更多特性。
这一步会在导入时完成,而不是在类进行实例化时。

元类的作用举例: * 验证属性 * 一次性把某个/种装饰器依附到多个方法上(记得以前写过一个装饰器来实现这个功能,因为那个类的 metaclass 被占了) * 序列化对象或转换数据 * 对象关系映射 * 基于对象的持久存储 * 动态转换使用其他语言编写的结构

Python 中各种类的关系

object 类和 type 类的关系很独特:objecttype 的实例,而 typeobject 的子类。 所有类都直接或间接地是 type 的实例,不过只有元类同时也是 type 的子类,因为元类从 ``type`` 类继承了构造类的能力

这里面的关系比较复杂,简单理一下 * 实例关系 * type 可以产出类,所以 type 的实例是类(isinstance(int, type)); * 元类继承自 type 类,所以元类也具有生成类实例的能力isinstance(Sequence, ABCMeta)) * 继承关系 * Python 中一切皆对象,所以所有类都是 object 的子类(object in int.__mro__) * 元类要继承type 对象,所以元类是 type 的子类(type in ABCMeta.__mro__

>>> # 构建一个元类
>>> class SomeMeta(type):
>>>     def __init__(cls, name, bases, dic):
>>>         # 这里 __init__ 的是 SomeClass,因为它是个类,所以我们直接用 cls 而不是 self 来命名它
>>>         print('Metaclass __init__')
>>>         # 为我们的类添加一个**类**属性
>>>         cls.a = 1
>>>
>>> class SomeClass(metaclass=SomeMeta):
>>>     # 在解释器编译这个类的最后,SomeMeta 的 __init__ 将被调用
>>>     print('Enter SomeClass')
>>>     def __init__(self, val):
>>>         # 这个函数在 SomeClass 实例化时才会被调用
>>>         self.val = val
>>>
>>>
>>> assert SomeClass.a == 1    # 元类为类添加的类属性
>>> sc = SomeClass(2)
>>> assert sc.val == 2
>>> assert sc.a == 1
>>> print(sc.__dict__, SomeClass.__dict__)
Enter SomeClass
Metaclass __init__
{'val': 2} {'__module__': '__main__', '__init__': <function SomeClass.__init__ at 0x7f46711fd9e0>, '__dict__': <attribute '__dict__' of 'SomeClass' objects>, '__weakref__': <attribute '__weakref__' of 'SomeClass' objects>, '__doc__': None, 'a': 1}

关于类构造过程,可以参见官方 Repo 中的代码执行练习(evaltime)部分

>>> # 用元类构造描述符
>>> from collections import OrderedDict
>>>
>>>
>>> class Field:
>>>     def __get__(self, instance, cls):
>>>         if instance is None:
>>>             return self
>>>         name = self.__name__
>>>         return instance._value_dict.get(name)
>>>
>>>     def __set__(self, instance, val):
>>>         name = self.__name__                    # 通过 _entity_name 属性拿到该字段的名称
>>>         instance._value_dict[name] = val
>>>
>>>
>>> class DesNameMeta(type):
>>>     @classmethod
>>>     def __prepare__(cls, name, bases):
>>>         """
>>>         Python 3 特有的方法,用于返回一个映射对象
>>>         然后传给 __init__ 的 dic 参数
>>>         """
>>>         return OrderedDict()
>>>
>>>     def __init__(cls, name, bases, dic):
>>>         field_names = []
>>>         for name, val in dic.items():
>>>             if isinstance(val, Field):
>>>                 val.__name__ = name            # 在生成类的时候,将属性名加到了描述符中
>>>                 field_names.append(name)
>>>         cls._field_names = field_names
>>>
>>>
>>>
>>> class NewDesNameMeta(type):                    # 使用 __new__ 方法构造新类
>>>     def __new__(cls, name, bases, dic):
>>>         for name, val in dic.items():
>>>             if isinstance(val, Field):
>>>                 val.__name__ = name
>>>         return super().__new__(cls, name, bases, dic)
>>>
>>>
>>> class SomeClass(metaclass=DesNameMeta):
>>>     name = Field()
>>>     title = Field()
>>>
>>>     def __init__(self):
>>>         self._value_dict = {}
>>>
>>>     def __iter__(self):
>>>         """
>>>         按定义顺序输出属性值
>>>         """
>>>         for field in self._field_names:
>>>             yield getattr(self, field)
>>>
>>>
>>> assert SomeClass.name.__name__ == 'name'
>>> sc = SomeClass()
>>> sc.name = 'Name'
>>> sc.title = 'Title'
>>> assert sc.name == 'Name'
>>> print(sc._value_dict)
>>> print(list(sc))
{'name': 'Name', 'title': 'Title'}
['Name', 'Title']

上面的例子只是演示作用,实际上在设计框架的时候,SomeClass 会设计为一个基类(models.Model),框架用户只要继承自 Model 即可正常使用 Field 中的属性名,而无须知道 DesNameMeta 的存在。

1.21.3. 类的一些特殊属性

类上有一些特殊属性,这些属性不会在 dir() 中被列出,访问它们可以获得类的一些元信息。
同时,元类可以更改这些属性,以定制类的某些行为。
  • cls.__bases__ 由类的基类组成的元组

  • cls.__qualname__ 类或函数的限定名称,即从模块的全局作用域到类的点分路径

  • cls.__subclasses__() 这个方法返回一个列表,包含类的直接子类

  • cls.__mro__ 类的方法解析顺序,这个属性是只读的,元类无法进行修改

  • cls.mro() 构建类时,如果需要获取储存在类属性 mro 中的超类元组,解释器会调用这个方法。元类可以覆盖这个方法,定制要构建的类解析方法的顺序。

1.21.4. 延伸阅读

`types.new_class <https://docs.python.org/3/library/types.html#types.new_class>`__ 和 types.prepare_class 可以辅助我们进行类元编程。

最后附上一个名言警句:

此外,不要在生产代码中定义抽象基类(或元类)……如果你很想这样做,我打赌可能是因为你想“找茬”,刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念,你(以及未来的代码维护者)的生活将更愉快,因为代码简洁明了。
——Alex Martelli