Python 是一门动态类型语言,没有编译器对变量类型正确性的检查与保证,这也意味着经常需要在运行时对变量的类型进行校验,尤其是在后端接口开发中,毕竟前端传入的数据往往是不可控的。
Python 3.5 引入了类型注解与 typing 模块,可以对 Python 代码进行静态类型检查,很大程度上提高了代码的可读性与可维护性,尤其是在较大的项目中。
除了静态类型检查,Python 的类型注解也可以在应用在运行时,例如 FastAPI(Pydantic) 就是利用了类型注解来进行请求参数解析、数据校验和 OpenAPI 文档生成的。
实际上 Python 标准库提供了一个简单的运行时类型检查的能力。
使用 isinstance
进行类型检查
isinstance 函数最常见的用法是判断一个对象是否是某个类型(及其子类)的实例,例如:
代码语言:javascript复制isinstance(1, int) # True
isinstance("hello", str) # True
isinstance(None, object) # True
使用isinstance
检查抽象类型
到了类型注解的时代,我们可以使用 isinstance
来检查一个对象是否实现了某个抽象接口,例如:
from typing import Callable, Iterable
isinstance(print, Callable) # True
isinstance([1, 2, 3], Iterable) # True
有一些遗憾的是,这里并不能为抽象类型添加范型参数(毕竟对容器的每个元素进行类型检查是一个非常耗时的事情),例如:
代码语言:javascript复制isinstance([1,2,3], list[int]) # TypeError: isinstance() argument 2 cannot be a parameterized generic
使用 isinstance
检查 typing.Protocol
自定义类型
Python 3.8 引入了 typing.Protocol 类与typing.runtime_checkable装饰器,可以用来定义类型,然后在运行时对对象进行类型检查。
代码语言:javascript复制from typing import Protocol, runtime_checkable
from dataclasses import dataclass
@runtime_checkable
class HasName(Protocol):
name: str
def say_hello(obj: HasName) -> None:
assert isinstance(obj, HasName), "obj must have a name attribute"
print(f"Hello {obj.name}")
@dataclass
class Person:
name: str
@dataclass
class Dog:
nick: str
say_hello(Person("John")) # Hello John
say_hello(Dog("Bobby")) # AssertionError: obj must have a name attribute
总结
虽然会带来一定的性能损耗,但是运行时的类型检查在很多场景下都是必要的,isinstance 函数可以帮助我们实现这一目的。 对于更复杂的类型检查,可以借助 dataclass 或者 pydantic。 值得一提的是 pydantic 的 2.0 版本使用 rust 上线了核心的数据校验逻辑,性能上有了很大的提升。