能否创建自定义类型提示生成函数?单位维度校验需求
解决方案:类型检查阶段的维度兼容性检测工具实现
一、先解决初始示例的语法问题
Python类型注解规范(PEP 484及后续)不允许函数调用语法(如f(T))作为返回类型,mypy的提示是合规的。你需要把f定义为符合规范的类型别名或泛型结构:
from typing import TypeVar, Generic, TypeAlias T = TypeVar('T') # 用类型别名替代函数调用,明确返回类型 f: TypeAlias = float class C(Generic[T]): def g(self) -> f: return 1.0 a = C().g() # 此时mypy和PyCharm都能正确识别a的类型为float
如果你的实际需求是f根据输入类型返回不同类型,可以用泛型Callable或自定义泛型类,但核心必须遵循[]的泛型语法规范。
二、核心需求:实现维度自动合并的类型检查
要让类型检查器解析DimMultiply[T, O]并自动合并维度,必须扩展类型检查器的行为——标准mypy/PyCharm不会自动处理自定义的类型运算逻辑。以下是具体实现方案:
1. 定义基础维度和操作标记
先明确维度类型和除法标记,用空类作为标识:
from typing import TypeVar, Tuple, Generic # 基础维度类型 class Length: pass class Time: pass class Mass: pass # 除法标记,用于区分分子/分母维度 class DIVIDE: pass # 维度元组的类型变量 D = TypeVar('D', bound=Tuple) D1 = TypeVar('D1', bound=Tuple) D2 = TypeVar('D2', bound=Tuple)
2. 编写mypy插件实现维度合并逻辑
mypy允许通过插件扩展类型解析能力,你需要编写一个插件来处理DimMultiply和DimDivide的类型展开:
from mypy.plugin import Plugin from mypy.types import Type, Instance, TupleType from collections import defaultdict class DimensionPlugin(Plugin): def get_generic_type_alias_expander(self, fullname: str): # 处理DimMultiply的类型展开 if fullname == "your_module.DimMultiply": def expand_multiply(args: list[Type]) -> Type: # 提取两个维度元组的内容 dims1 = args[0].items if isinstance(args[0], TupleType) else [] dims2 = args[1].items if isinstance(args[1], TupleType) else [] # 统计每个维度的净次数(分子+1,分母-1) dim_count = defaultdict(int) is_denominator = False for dim in dims1 + dims2: if isinstance(dim, Instance) and dim.type.fullname == "your_module.DIVIDE": is_denominator = True continue if is_denominator: dim_count[dim] -= 1 is_denominator = False else: dim_count[dim] += 1 # 重新构建合并后的维度元组 merged_dims = [] for dim, cnt in dim_count.items(): if cnt > 0: merged_dims.extend([dim] * cnt) elif cnt < 0: merged_dims.append(Instance(type=self.lookup_typeinfo("your_module.DIVIDE"), args=[])) merged_dims.extend([dim] * (-cnt)) # 返回合并后的Tuple类型 tuple_typeinfo = self.lookup_typeinfo("builtins.tuple") return TupleType(items=merged_dims, fallback=tuple_typeinfo) return expand_multiply # 处理DimDivide:等价于乘以带DIVIDE的目标维度 elif fullname == "your_module.DimDivide": def expand_divide(args: list[Type]) -> Type: divide_mark = Instance(type=self.lookup_typeinfo("your_module.DIVIDE"), args=[]) # 把除数维度转为"DIVIDE + 维度"的形式,复用乘法逻辑 denominator_dims = TupleType( items=[divide_mark] + args[1].items, fallback=self.lookup_typeinfo("builtins.tuple") ) return expand_multiply([args[0], denominator_dims]) return expand_divide return None # 插件入口 def plugin(version: str): return DimensionPlugin
然后在mypy.ini中启用插件:
[mypy] plugins = path/to/your_dimension_plugin.py
3. 定义带维度的Value类
现在可以编写Value类,用泛型参数表示维度,并重写运算方法的返回类型:
from typing import Generic, TypeVar V = TypeVar('V', bound='Value') class Value(Generic[D]): def __mul__(self: Value[D1], other: Value[D2]) -> Value[DimMultiply[D1, D2]]: # 运行时逻辑:实际计算值和维度 return Value() def __truediv__(self: Value[D1], other: Value[D2]) -> Value[DimDivide[D1, D2]]: return Value() def __add__(self: Value[D], other: Value[D]) -> Value[D]: # 只有相同维度才能加减,类型检查器会自动验证 return Value() def __sub__(self: Value[D], other: Value[D]) -> Value[D]: return Value() # 定义具体单位 ft = Value[Tuple[Length]]() lbs = Value[Tuple[Mass]]()
4. 测试类型检查
现在编写测试代码,mypy会自动检测维度不兼容的加减操作:
x = 1 * ft y = 3 * lbs z = x + y # mypy会抛出类型错误:Value[Tuple[Length]] 和 Value[Tuple[Mass]] 不兼容
三、PyCharm的支持方案
PyCharm对自定义类型插件的支持不如mypy灵活,你可以通过以下方式适配:
- 显式类型别名:提前定义常用的合并维度类型,比如
LengthOverTime = DimDivide[Tuple[Length], Tuple[Time]],PyCharm会识别这些别名。 - 利用泛型推断:PyCharm能识别简单的泛型参数传递,复杂的维度合并可以通过类型别名辅助。
- 双工具配合:先用mypy作为主要类型检查工具,PyCharm负责日常编码的基础类型提示。
内容的提问来源于stack exchange,提问作者Rick
相关产品推荐
相关产品推荐

