Python 3.9+中物理单位类型提示的简洁合规实现方案问询
解决方案:为Dataclass物理量添加单位类型提示并通过Mypy检查
问题分析
你尝试用TypeVar/Annotated/NewType标记物理单位时出现三个核心问题:
TypeVar是泛型工具,不能直接作为字段类型使用,必须绑定到泛型类或函数。- 直接将
Annotated赋值给变量后作为类型,Mypy会识别为变量而非合法类型别名。 NewType是强类型封装,必须显式构造实例,无法直接接收普通数值类型。
正确实现方案
我们使用Annotated(携带单位元数据)+ 类型别名的方式,配合__post_init__完成单位转换,同时满足Mypy检查要求,且允许__init__直接接收数值类型。
完整代码示例
from dataclasses import dataclass, fields from typing import Annotated, get_args, get_origin # 定义带单位元数据的类型别名(符合Mypy类型规范) Meter = Annotated[float, "meter"] Um = Annotated[float, "um"] Ohm = Annotated[float, "ohm"] # 单位转SI制的系数映射,统一管理转换规则 UNIT_TO_SI = { "meter": 1.0, "um": 1e-6, "ohm": 1.0 } @dataclass class Data: length_1: Meter = 10e-3 # 默认值已为SI单位(米) length_2: Um = 20 # 默认值为微米,将自动转为米 R: Ohm = 50 # 欧姆为SI单位,无需转换 def __post_init__(self): # 遍历所有字段,自动完成单位转换 for field in fields(self): field_type = field.type # 检查字段类型是否为Annotated if get_origin(field_type) is Annotated: base_type, unit = get_args(field_type) # 确认基础类型为数值类型,且单位存在转换规则 if issubclass(base_type, (int, float)) and unit in UNIT_TO_SI: raw_value = getattr(self, field.name) # 转换为SI单位并覆盖原字段值 setattr(self, field.name, raw_value * UNIT_TO_SI[unit])
关键细节说明
- 类型别名规范:
用Annotated[float, "单位"]定义类型别名,Mypy会将其识别为合法类型,同时保留单位元数据。 - 单位转换逻辑:
在__post_init__中通过dataclasses.fields获取字段信息,用get_origin和get_args解析Annotated的元数据,自动完成SI转换,避免重复编写转换代码。 - Mypy兼容性:
- 允许直接传入
int/float,因为底层类型是float,Mypy支持int隐式转换为float。 - 所有类型注解均符合Mypy规范,运行
mypy units.py无报错。
- 允许直接传入
原错误修正说明
- TypeVar误用:
TypeVar用于泛型参数绑定,不适合标记单位,替换为Annotated类型别名即可解决“未绑定”错误。 - Annotated变量问题:直接将
Annotated赋值给变量后作为类型,Mypy会视为普通变量;改为类型别名(如Um = Annotated[float, "um"])则符合类型规范。 - NewType强类型问题:
NewType要求显式构造实例(如ohm(50)),不符合你希望直接接收数值的需求,Annotated既保留单位提示,又允许直接使用数值类型。
内容的提问来源于stack exchange,提问作者hoolabandoola
相关产品推荐
相关产品推荐

