Python 3.10中如何为建造者模式make方法添加类型提示
该需求在Python 3.10版本中完全可以实现,不需要升级Python版本,通过标准库typing模块提供的泛型能力即可完成,配置后mypy、PyCharm、VSCode(Pylance/Pyright)等类型检查/IDE工具都能正确推断make()方法的返回值类型,支持对应类的属性、方法自动补全,不会再被推断为None。
原先代码被推断返回
None的原因是:基类AggregateMaker中没有声明_make方法的签名,类型检查器无法追踪self._make()的返回值类型,就会默认回退为None类型。
核心思路是给AggregateMaker基类定义泛型参数,绑定子类_make方法返回的具体产品类型,同时给链式调用的new、with_方法标注正确的自身类型,避免链式调用时丢失类型信息。
第一步:导入依赖的类型构件
Python 3.10标准库自带所有需要的类型组件,不需要额外安装第三方包:
from dataclasses import dataclass # 导入泛型相关类型 from typing import TypeVar, Generic, Any
第二步:定义类型变量
需要定义两个类型变量,分别标记最终生成的产品类型、Maker类自身的类型(用于链式调用返回值标注):
# 泛型参数:标记make方法最终返回的产品类型(Fruit/Tea等) T = TypeVar("T") # 泛型参数:标记Maker子类自身类型,解决链式调用的类型传递问题 M = TypeVar("M", bound="AggregateMaker[Any]")
第三步:改造基类为泛型类
给AggregateMaker添加泛型继承,给所有方法补充类型标注,同时在基类声明_make方法的接口,方便类型检查器识别返回值传递链路:
class AggregateMaker(Generic[T]): _fields: dict[str, Any] @classmethod def new(cls: type[M], **fields: Any) -> M: return cls(fields=None).with_(**fields) def make(self) -> T: return self._make(self._fields) # 基类声明_make接口,子类实现时自动绑定对应T类型 def _make(self, fields: dict[str, Any]) -> T: raise NotImplementedError def with_(self: M, **overrides: Any) -> M: copy = dict(self._fields) for name, value in overrides.items(): copy[name] = value return type(self)(copy)
第四步:子类指定泛型参数
子类继承基类时,在方括号中指定当前子类生成的具体产品类型即可,其余原有业务逻辑不需要改动:
# 指定泛型参数为Fruit class FruitMaker(AggregateMaker[Fruit]): def __init__(self, fields: dict[str, Any] | None): if fields is None: fields = { "name": None, "smell": None, } self._fields = fields def _make(self, fields: dict[str, Any]) -> Fruit: return Fruit(**fields) # 指定泛型参数为Tea class TeaMaker(AggregateMaker[Tea]): def __init__(self, fields: dict[str, Any] | None): if fields is None: fields = { "name": None, "hot": None, } self._fields = fields def _make(self, fields: dict[str, Any]) -> Tea: return Tea(**fields)
修改完成后,原有测试用例不需要任何改动即可正常运行,同时返回值类型可以被正确识别:
FruitMaker.new().with_(name="Durian").with_(smell="Strong").make()会被推断为Fruit类型,自动补全name、smell属性TeaMaker.new(name="Camomile", hot=True).make()会被推断为Tea类型,自动补全name、hot属性
如果你在项目中已经引入了
typing_extensions兼容包,可以直接使用其中的Self类型替换我们自定义的M类型变量,写法会更简洁,不需要手动定义自身类型的TypeVar。
内容的提问来源于stack exchange,提问作者André Ricardo

