在Python中进行运行时类型检查

2023-05-12 08:41:16 浏览数 (1)

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 来检查一个对象是否实现了某个抽象接口,例如:

代码语言:javascript复制
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 上线了核心的数据校验逻辑,性能上有了很大的提升。

0 人点赞