多态和泛型
多态性是指在不考虑实例类型的情况下使用实例
元类
type(obj) -> class 众所周知 type 可以返回对象的类型
type 还可以把对于类的描述作为参数,并返回一个类
MyClass = type("MyClass", (), {})
这种用法就是由于 type 实际上是一个元类,作为元类的 type 在 Python 中被用于在后台创建所有的类。也可以叫它类工厂。事实上所有类默认使用 type 创建,你也可以指定预设配置的类工厂
class Simple1(object, metaclass=...): ...
鸭子类型
如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子
判断一个对象的类型 只需要看它是否实现了那个类型该有的方法和属性
比如,在抽象类中有一个 Sized 类型可以判断对象是否实现了长度
泛函数
python3.4+
第一个参数的数据类型不同,其调用的函数也就不同
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
它们可用于判断一个具体类是否具有某一特定的接口
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 模块通常都有别名
抽象元类
元类即类的类
声明一个抽象类的时候需要继承抽象类的元类,然后再在其上声明抽象方法(即需要实现而未实现的方法)
存在抽象方法时,实例化/使用抽象方法的时候就会报错。
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__
属性中
简单的类型注解
注解参数的类型 ->
后面是返回值的类型
def foo(step: int) -> int: ...
结合默认参数一起使用
def foo(step: int = 1) -> int: ...
类型注解不做静态检查,只在 IDE 提供提示
你可以使用基本类型作为注解,也可以使用抽象类型
python3.5+ typing
模块中有很多抽象类型
比如 typing.Mapping
是键值对类型 是广义上的dict
除此之外还有typing.NewType
typing.TypeAlias
较为常用
给定字面量
python3.8+ 和指定枚举类型各有利弊
MODE = typing.Literal["r", "rb", "w", "wb"]
def open_(mod: MODE):
pass
自引用
使用类名字符串注解或实验性语法 annotations 来兼容自引用
from __future__ import annotations
class C:
chirdren: List[C] # or List["C"]
python3.11+ 允许使用 typing.Self
作为自己类型
泛型注解(GenericAlias)
python 中泛型使用 sub 即中括号[]
,低版本只有typing
模块的抽象类型支持泛型
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
# 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 是注释自身的推荐做法
import typing
T = typing.TypeVar("T", bound="C")
class C:
@classmethod
def fromJson(cls: typing.Type[T], json: str) -> T: ...
容器的泛型和返回值参数一样这样表示:
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 或其他子类来定义。
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 的模型中使用:
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
装饰器可以实现自定义验证和对象之间的复杂关系