Skip to content

多态和泛型

多态性是指在不考虑实例类型的情况下使用实例

元类

type(obj) -> class 众所周知 type 可以返回对象的类型

type 还可以把对于类的描述作为参数,并返回一个类

python
MyClass = type("MyClass", (), {})

这种用法就是由于 type 实际上是一个元类,作为元类的 type 在 Python 中被用于在后台创建所有的类。也可以叫它类工厂。事实上所有类默认使用 type 创建,你也可以指定预设配置的类工厂

python
class Simple1(object, metaclass=...): ...

鸭子类型

如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子

判断一个对象的类型 只需要看它是否实现了那个类型该有的方法和属性

比如,在抽象类中有一个 Sized 类型可以判断对象是否实现了长度

泛函数

python3.4+

第一个参数的数据类型不同,其调用的函数也就不同

python
from functools import singledispatch


@singledispatch
def age(obj):
    print("请传入合法类型的参数!")


@age.register(int)
def _(age):
    print("我已经{}岁了。".format(age))


@age.register(str)
def _(age):
    print("I am {} years old.".format(age))


age(23)  # int
age("twenty three")  # str
age(["23"])  # list

抽象基类

python3.3+ collections.abc

它们可用于判断一个具体类是否具有某一特定的接口

python
from collections.abc import Iterable  # 可迭代对象的抽象基类
from collections.abc import Mapping  # 字典的抽象基类
from collections.abc import Sequence  # 元组的抽象基类

# 还有很多基类,低版本python可以不需要.abc 从collections也可以导入
print(isinstance(map(lambda x: x * x, range(5)), Iterable))
# True

collections.abc 里的抽象基类在 typing 模块通常都有别名

抽象元类

元类即类的类

声明一个抽象类的时候需要继承抽象类的元类,然后再在其上声明抽象方法(即需要实现而未实现的方法)

存在抽象方法时,实例化/使用抽象方法的时候就会报错。

python
from abc import ABCMeta, abstractmethod


class PayABC(metaclass=ABCMeta):
    @abstractmethod
    def pay(self, money):
        # 也可以raise NotImplementedError
        pass


class Wechatpay(PayABC):
    def pay(self, money):
        print("微信支付了%s元" % money)

Type Hint

建议参考这篇教程翻译

python 的 Type Hint 在运行时不会被擦除,而不保存在函数或模块的__annotations__属性中

简单的类型注解

注解参数的类型 ->后面是返回值的类型

python
def foo(step: int) -> int: ...

结合默认参数一起使用

python
def foo(step: int = 1) -> int: ...

类型注解不做静态检查,只在 IDE 提供提示

你可以使用基本类型作为注解,也可以使用抽象类型

python3.5+ typing模块中有很多抽象类型

比如 typing.Mapping 是键值对类型 是广义上的dict

除此之外还有typing.NewType typing.TypeAlias较为常用

给定字面量

python3.8+ 和指定枚举类型各有利弊

python
MODE = typing.Literal["r", "rb", "w", "wb"]


def open_(mod: MODE):
    pass

自引用

使用类名字符串注解或实验性语法 annotations 来兼容自引用

python
from __future__ import annotations


class C:
    chirdren: List[C]  # or List["C"]

python3.11+ 允许使用 typing.Self作为自己类型

泛型注解(GenericAlias)

python 中泛型使用 sub 即中括号[],低版本只有typing模块的抽象类型支持泛型

python
from typing import Callable, Iterable, Union


# 注解:参数是一个函数或者函数列表
def retry(tasks: Union[Iterable[Callable], Callable]):
    pass


# 除了作为注解依然可以用来判断是否是可迭代对象
print(isinstance(map(lambda x: x * x, range(5)), Iterable))
# True

python3.8+ 使用 typing.Final 可以注解常量,常量声明后只能赋值一次

python3.9+ 使用 list 而不必导入 typing.List

标准库中的其他一些类型现在同样也是通用的,例如 queue.Queue

python3.10+ 允许使用 | 代替typing.Union

python
# python3.8
def f(arr: List[Union(int, str)], param: Optional(int)) -> Union(float, str):
    pass


# python3.10+
def f(arr: list[int | str], param: int | None) -> float | str:
    pass

只需实现了__class_getitem__即可使用[]传递泛型参数

它还可以用types.GenericAlias实例化得到

值得注意的是tuple,它和 typescript 里一样是定长的。不定长需要用如tuple[int, ...]

泛型参数化

低版本自定义结构 typing.TypeVar 是注释自身的推荐做法

python
import typing

T = typing.TypeVar("T", bound="C")


class C:
    @classmethod
    def fromJson(cls: typing.Type[T], json: str) -> T: ...

容器的泛型和返回值参数一样这样表示:

python
from typing import TypeVar, Sequence

T = TypeVar("T")  # Declare type variable


def first(l: Sequence[T]) -> T:  # Generic function
    return l[0]

pydantic

pydantic 库是第三方用于数据接口定义检查与设置管理的库。

兼容 python3.7 的 @dataclass

基本用法

pydantic 在运行时强制执行类型提示,并在数据无效时提供友好的错误。

需要继承 BaseModel 或其他子类来定义。

python
from __future__ import annotations
from pydantic import BaseModel
from enum import Enum


class HTTP_method(Enum):
    get = "GET"
    post = "POST"
    put = "PUT"
    delete = "DELETE"
    patch = "PATCH"

    def __repr__(self):
        return repr(self.value)


class Setting(BaseModel):
    url: str
    method: HTTP_method = HTTP_method.get
    title: str
    id: str
    children: list[Setting] = list()


Setting.update_forward_refs()

setting1 = Setting(**{"url": "/items", "title": "测试", "id": "001"})
setting2 = Setting(url="/item", title="测试", id="002", method="DELETE")
setting = Setting(url="/items", title="测试", id="000")
setting.children = [setting1, setting2]
print(setting.json())

定义属性支持常用内建类型、枚举、日期等类型和其部分泛型,以及其他 pydantic 定义的类型

实例化将执行所有解析和验证,如果缺省触发将会被赋予一个深拷贝后的默认值,如果有错误则会触发 ValidationError 异常

允许类型嵌套,但自引用需要使用.update_forward_refs()类方法

定义的属性支持使用下标获取,使用.json() .dict()等方法可以转为其他格式

泛型检查

使用 typing.TypeVar 的实例作为参数,传递给 typing.Generic,然后在继承了 pydantic.generics.GenericModel 的模型中使用:

python
from typing import Generic, TypeVar, Optional, List
from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel

DataT = TypeVar("DataT")


class Error(BaseModel):
    code: int
    message: str


class DataModel(BaseModel):
    numbers: List[int]
    people: List[str]


class Response(GenericModel, Generic[DataT]):
    data: Optional[DataT]
    error: Optional[Error]

    @validator("error", always=True)
    def check_consistency(cls, v, values):
        if v is not None and values["data"] is not None:
            raise ValueError("must not provide both data and error")
        if v is None and values.get("data") is None:
            raise ValueError("must provide data or error")
        return v


data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message="Not found")

Response[int](data=1)
# "data=1 error=None"
Response[str](data="value")
# "data='value' error=None"
Response[str](data="value").dict()
# "{'data': 'value', 'error': None}"
Response[DataModel](data=data).dict()
# "{'data': {'numbers': [1, 2, 3], 'people': []}, 'error': None,}"
Response[DataModel](error=error).dict()
# "{'data': None, 'error': {'code': 404, 'message': 'Not found'},}"

try:
    Response[int](data="value")
except ValidationError as e:
    print(e)

"""2 validation errors for Response[int]
data
    value is not a valid integer (type=type_error.integer)
error
    must provide data or error (type=value_error)"""

使用validator装饰器可以实现自定义验证和对象之间的复杂关系