Pyhton - 类元编程

类元编程是指在运行时创建或定制类的技艺。在Python 中,类是一等对象,因此任何时候都可以使用函数新建类,无须使用class关键字。类装饰器也是函数,不过能够审查、修改,甚至把被装饰的类替换成另一个类。最后元类是类元编程最高级的工具:使用元类可以创建具有某种特质的全新类,例如前面讲过的抽象类。

身为对象的类

与Python 中的很多程序实体一样,类也是对象。Python数据模型为每个类定义了很多属性,除了__class__、namemro。还有以下标准属性。

cls._bases_:

由类的基类构成的元组。

cls._qualname_:
类或函数的限定名称,即从模块的全局作用域到类的点分路径。在一个类中定义另一个类是会用到这个属性。例如,Django模型类Ox 内部有一个名为Meta的类。Meta类的__qualname__是Ox.Meta,但是__name__只是Meta.

cls._subclasses_():
这个方法会返回包含类的直接子类的列表,其实现是使用弱引用、以防止在超类和子类之间出现循环引用(子类在__bases_属性中存储指向超类的强引用)。这个方法返回的列表中是内存里现存的子类,不含尚未导入的模块在中的子类。

cls.mro():

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

type:内置的类工厂函数

我们通常认为type 是一个函数,会返回对象所属的类,即type(my_object) 返回 my_object._class_

然而,type是一个类,在调用时会传入3个参数,创建一个新类

以下面这个简单的类为例

class MyClass(MySuperClass,MyMixin):
  x = 42,
  def x2(self):
    return self.x * 2

使用type 构造函数,可以在运行时创建MyClass类,如下所示。
  MyClass  = type('MyClass', (MySuperClass,MyMixin),{'x':42,  'x2': lambda self: self.x * 2},)

这个type 调用与前面的class MyClass... 语句块 在功能上是等同的。

Python 读取class 语句时会调用type构建类对象,传入的参数如下所示。

  • name: class 关键字后的标识符,例如MyClass

  • bases: 类标识符后面圆括号内提供的超类元组,如果class 语句没有提到超类,则为(object,)

  • dict: 属性名称到值得映射。可调用对象变成方法,其他值变成类属性

type 类是一个元类,即构建类得类。也就是说,type 类得实例还是类。标准库还提供了一些其他元类,不过type 是默认得元类

>>> type(7)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(OSError)
<class 'type'>
>>> class Whatever:
...     pass
...
>>> type(Whatever)
<class 'type'>
>>>

类工厂函数

collections.namedtuple、typing.NamedTuple 和 @dataclass。这些类构建起都用到了本章涵盖得技术。

首先,我们构建一个特别简单得工厂函数,用于创建可变对象得类 ———— 算是@dataclass 最简单得替代品

假设我在编写一个简单得宠物店应用程序,想以简单得记录存储狗的数据。但是,我不想编写像下面这样得样板代码:

>>> type(7)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(OSError)
<class 'type'>
>>> class Whatever:
...     pass
...
>>> type(Whatever)
<class 'type'>
>>>
from typing import Union, Any
from collections.abc import Iterator, Iterable

FieldName = Union[str, Iterable[str]]  # 1


def record_factory(cls_name: str, field_names: FieldName) -> type[tuple]: # 2
    slots = parse_indentifies(field_names) # 3

    def __init__(self, *args, **kwargs) -> None: # 4
        attrs = dict(zip(self.__slots__, args))
        attrs.update(kwargs)

        for name, value in attrs.items():
            setattr(self, name, value)

    def __iter__(self) -> Iterator[Any]: # 5
        for name in self.__slots__:
            yield getattr(self, name)

    def __repr__(self): # 6
        values = ', '.join(f'{name}={value!r}'
                           for name, value in zip(self.__slots__, self))
        cls_name = self.__class__.__name__
        return f'{cls_name}({values})'

    cls_attrs = dict( # 7
        __slots__=slots,
        __init__=__init__,
        __iter__=__iter__,
        __repr__=__repr__
    )

    return type(cls_name, (object,), cls_attrs) # 8


def parse_indentifies(names: FieldName) -> tuple[str, ...]:
    if isinstance(names, str):
        names.replace(',', ' ').split() # 9
    if not all(s.isidentifier() for s in names):
        raise ValueError('names must all be valid identifiers')
    return tuple(names)
posted @ 2024-04-23 22:03  chuangzhou  阅读(11)  评论(0编辑  收藏  举报