Python类变量内置类型限制与运行时类型校验的更优实现方案问询
首先明确:Python原生的类型注解属于静态检查标识,仅会被IDE、mypy等静态检查工具识别,运行时解释器会直接忽略,所以你前两次的写法都不会在运行时报错。另外你第一次代码里写的str or int是错误的多类型注解写法,正确写法为typing.Union[str, int](Python 3.10+支持简写为str | int),原写法连静态检查工具也无法正确识别。
除了手写带isinstance的setter外,还有以下几种更简洁易维护的实现方式:
方案1:使用Pydantic(生产环境首选)
Pydantic是专门用于运行时数据校验、序列化的第三方库,会自动根据类型注解完成入参校验,不符合规则直接抛出异常,无需手写任何校验逻辑:
- 安装依赖:
pip install pydantic - 代码示例:
from pydantic import BaseModel from typing import Union class Address(BaseModel): city: str building_number: Union[str, int] apartment_number: int street: str
实例化时如果入参类型不符合要求,会直接抛出ValidationError,同时还支持自定义校验规则、默认值、数据序列化等扩展功能,是目前Python领域做数据类校验的主流方案。
方案2:标准库实现(无第三方依赖)
如果你不想引入额外依赖,可以用Python 3.7+自带的dataclasses配合__post_init__方法统一做校验,比逐个属性写setter维护成本低很多:
from dataclasses import dataclass from typing import Union @dataclass class Address: city: str building_number: Union[str, int] apartment_number: int street: str def __post_init__(self): # 所有校验逻辑统一放在此处 if not isinstance(self.city, str): raise TypeError(f"city预期为str类型,实际得到{type(self.city).__name__}") if not isinstance(self.building_number, (str, int)): raise TypeError(f"building_number预期为str或int类型,实际得到{type(self.building_number).__name__}") if not isinstance(self.apartment_number, int): raise TypeError(f"apartment_number预期为int类型,实际得到{type(self.apartment_number).__name__}") if not isinstance(self.street, str): raise TypeError(f"street预期为str类型,实际得到{type(self.street).__name__}")
方案3:描述符实现(适合多类复用校验规则)
如果有多个类都需要相同的类型校验规则,可以封装通用的类型校验描述符,实现校验逻辑一次编写多处复用:
class TypedField: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, cls): return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, self.expected_type): raise TypeError(f"{self.name}预期为{self.expected_type}类型,实际得到{type(value).__name__}") instance.__dict__[self.name] = value class Address: city = TypedField('city', str) building_number = TypedField('building_number', (str, int)) apartment_number = TypedField('apartment_number', int) street = TypedField('street', str) def __init__(self, city, building_number, apartment_number, street): self.city = city self.building_number = building_number self.apartment_number = apartment_number self.street = street
内容的提问来源于stack exchange,提问作者xGPlayer
相关产品推荐
相关产品推荐

