如何通过类成员自动推断泛型类类型,避免重复指定?
如何避免重复指定泛型类型与类成员类型?
我们可以用TypeVar和Generic实现支持类型推断的类,比如这个经典的盒子示例:
from typing import TypeVar, Generic T = TypeVar('T') class Box(Generic[T]): def __init__(self, content: T) -> None: self.content = content Box(1) # 类型检查器会自动推断出这是 Box[int]
但在更复杂的场景里,比如关联不同运动员和他们的统计数据时,就会遇到类型推断和运行时实现重复代码的问题。先看场景代码:
首先是统计数据的抽象类和具体实现:
from abc import ABC from typing import Generic, TypeVar, Type class Stats(ABC): pass class BaseballStats(Stats): @property def batting_average(self) -> float: return 0.314 class FootballStats(Stats): @property def yards(self) -> int: return 314
然后我们定义运动员抽象类,用泛型关联对应的统计类型:
S = TypeVar('S', bound=Stats) class Athlete(ABC, Generic[S]): _stats_type: Type[S] @property def stats(self) -> S: return self._stats_type()
现在有两个头疼的情况:
- 只在子类里赋值
_stats_type,类型检查器没法推断stats的类型:class BaseballPlayer(Athlete): _stats_type = BaseballStats bo = BaseballPlayer() # 用 reveal_type 查看会发现 bo.stats 是 Any 类型 print(bo.stats.batting_average) # 运行时能正常工作,但类型检查没提示 - 只指定泛型参数不赋值
_stats_type,类型检查器能识别,但运行时直接报错:class FootballPlayer(Athlete[FootballStats]): pass bo = FootballPlayer() # reveal_type 显示 bo.stats 是 FootballStats,但运行时会炸 print(bo.stats.yards) # AttributeError: 'FootballPlayer' object has no attribute '_stats_type'
能不能不用像下面这样重复写两遍统计类型?
class FootballPlayer(Athlete[FootballStats]): _stats_type = FootballStats
可行方案:用元类自动绑定泛型
答案是肯定的,我们可以用元类在类定义阶段自动完成泛型参数的绑定,同时确保运行时有正确的_stats_type值。
代码实现如下:
from abc import ABC, ABCMeta from typing import Generic, TypeVar, Type, cast S = TypeVar('S', bound=Stats) class AthleteMeta(ABCMeta, type(Generic[S])): def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, object]) -> type: # 检查当前类有没有定义 _stats_type stats_type = namespace.get('_stats_type') if stats_type is not None and issubclass(stats_type, Stats): # 把父类替换成绑定了 stats_type 的 Athlete 泛型类 # 这里用 cast 是为了让类型检查器不报错 bases = cast(tuple[type, ...], (Athlete[stats_type],) + bases[1:]) return super().__new__(cls, name, bases, namespace) class Athlete(ABC, Generic[S], metaclass=AthleteMeta): _stats_type: Type[S] @property def stats(self) -> S: return self._stats_type()
现在子类只需要写一行_stats_type赋值,类型检查和运行时都能正常工作:
class BaseballPlayer(Athlete): _stats_type = BaseballStats bo = BaseballPlayer() # reveal_type(bo.stats) 现在会正确返回 BaseballStats print(bo.stats.batting_average) # 类型检查有提示,运行时也正常 class FootballPlayer(Athlete): _stats_type = FootballStats bo = FootballPlayer() # reveal_type(bo.stats) 正确返回 FootballStats print(bo.stats.yards) # 一切正常
原理说明
- 元类
AthleteMeta同时继承了ABCMeta和泛型的元类,这样既支持抽象类的特性,又能处理泛型绑定。 - 在子类被定义的时候,元类的
__new__方法会检查子类有没有设置_stats_type,如果有,就自动把父类替换成绑定了该类型的Athlete[stats_type]。 - 这样一来,类型检查器能自动识别泛型参数,运行时也有了正确的
_stats_type,不用再重复写两遍类型。
内容的提问来源于stack exchange,提问作者darda
相关产品推荐
相关产品推荐

