Python:如何为数量可变的Metric类型类属性添加类型注解?
问题:如何在基类中正确提示数量可变的同类型属性?
我有一组类,需要遍历其中类型为Metric的属性。这组类包含一个抽象基类父类和多个子类,子类都拥有数量不等的Metric类型类属性,且继承了父类的__iter__方法来遍历这些属性。
我选择使用可迭代属性而非字典,是因为希望对象具备类型提示,同时又能按名称调用指标,还能按顺序遍历。因此我需要实现两种操作:
Metrics.metric_1(按名称调用)for metric in Metrics:(遍历)
目前我的做法是添加几个属性注解并使用省略号:
from abc import ABC class MetricsBase(ABC): metric_1: Metric metric_2: Metric ... @classmethod def __iter__(cls): for attr, value in cls.__dict__.items(): if not attr.startswith("__"): yield value class MetricChild(MetricsBase): metric_1 = Metric(x) metric_2 = Metric(y) metric_3 = Metric(z)
但我不确定这种方式是否符合Python风格或是否正确,想知道有没有更简洁的实现方式。
解决方案
1. 使用typing.ClassVar结合__getattr__(推荐)
这种方式能让类型检查工具自动识别子类中任意Metric类型的类属性,同时保持代码简洁:
from abc import ABC from typing import ClassVar, Iterator, Metric class MetricsBase(ABC): @classmethod def __iter__(cls) -> Iterator[Metric]: # 只返回真正的Metric实例,过滤系统属性和非Metric属性 for attr_name in dir(cls): if attr_name.startswith("__"): continue attr_value = getattr(cls, attr_name) if isinstance(attr_value, Metric): yield attr_value # 类型提示:任意名称的类属性都是Metric类型 @classmethod def __getattr__(cls, name: str) -> ClassVar[Metric]: raise AttributeError(f"{cls.__name__} has no attribute {name}")
优势:
- 无需手动逐个注解属性,自动适配子类任意数量的
Metric属性 __iter__逻辑更严谨,避免误返回非Metric的类属性- 类型检查工具(如mypy、PyCharm)会正确识别
Metrics.metric_x的类型
2. 用Protocol定义属性契约(严格类型场景)
如果需要更严格的类型约束,可以通过协议强制子类遵循“包含任意Metric属性+可迭代”的契约:
from abc import ABC from typing import Protocol, Iterator, Metric, runtime_checkable @runtime_checkable class MetricContainer(Protocol): # 索引签名提示:任意属性都是Metric类型的类变量 __getitem__: ClassVar[Metric] def __iter__(self) -> Iterator[Metric]: ... class MetricsBase(ABC, MetricContainer): @classmethod def __iter__(cls) -> Iterator[Metric]: for attr_name in dir(cls): if attr_name.startswith("__"): continue attr_value = getattr(cls, attr_name) if isinstance(attr_value, Metric): yield attr_value
这种方式下,所有继承MetricsBase的类都会被类型检查工具识别为合法的Metric容器,同时支持迭代操作。
3. 优化现有注解方式
如果想保留类似原代码的写法,可以直接注解__dict__的类型,替代手动写多个metric_1:
from abc import ABC from typing import Iterator, Metric class MetricsBase(ABC): # 直接标注类属性字典的所有值都是Metric类型 __dict__: dict[str, Metric] @classmethod def __iter__(cls) -> Iterator[Metric]: for attr_value in cls.__dict__.values(): if isinstance(attr_value, Metric): yield attr_value
这种写法比原代码更简洁,也能让类型检查工具理解类属性的类型规则。
内容的提问来源于stack exchange,提问作者Archie
相关产品推荐
相关产品推荐

