如何为Genome类异质列表添加动态类型提示以移除type:ignore
解决Genome类__mul__方法的动态类型提示问题
问题背景
我正在编写模拟生物遗传机制的Python代码,其中Genome类可由Gene或Genome自身组成异质列表,用于模拟单基因控制(如眼色)和多基因协同控制(如血型)的遗传机制。当前Genome的__mul__方法需要添加精准的动态类型提示,让类型检查器识别同一类的父代与母代序列对应类型一致,从而移除# type: ignore注释,实现完全类型可检查的代码。
核心问题代码片段:
class Genome(list['Gene | Genome']): # ... 其他代码 ... def __mul__(self, mother: Self) -> Self: return type(self)(*(gene * mother[sequence] for sequence, gene in enumerate(self))) # type: ignore - Because gene * mother[sequence] is Gene | Genome x Gene | Genome
示例用法:
class ABOAntigens(Allele): A = 'A', 1 B = 'B', 1 O = 'O', 0 class ABO(Gene, Allele=ABOAntigens): pass class RhesusAntigen(Allele): Positive = '+', 1 Negative = '-', 0 class Rhesus(Gene, Allele=RhesusAntigen): pass class Blood(Genome, genome=[ABO, Rhesus]): pass class EyeColour(Allele): Brown = 'Brown', 2 Green = 'Green', 1 Blue = 'Blue', 0 class Eye(Gene, Allele=EyeColour): pass class Human(Genome, genome=[Eye, Blood]): pass
解决方案
要让类型检查器正确识别序列对应类型的匹配关系,需结合泛型(Generics)、可变类型参数和Self类型实现。具体步骤如下:
1. 导入必要的类型工具
确保导入Python typing模块中的相关工具(Python 3.11+支持TypeVarTuple和Unpack):
from typing import Generic, TypeVarTuple, Unpack, Self, Type import itertools import random from enum import StrEnum
2. 定义可变长度的类型变量
用TypeVarTuple定义可变数量的类型参数,适配Genome子类中任意长度的序列类型:
T = TypeVarTuple('T')
3. 将Genome改造为泛型类
让Genome继承Generic[Unpack[T]],并在__init_subclass__中绑定子类的具体序列类型:
class Genome(list[Unpack[T]], Generic[Unpack[T]]): GENOME: list[Type[Unpack[T]]] = [] def __init_subclass__(cls, *args, genome: list[Type[Unpack[T]]], **kwargs) -> None: super().__init_subclass__(*args, **kwargs) cls.GENOME = genome
4. 完善__mul__方法的类型提示
现在可以给__mul__方法添加精确的类型提示,移除type: ignore注释:
def __mul__(self, mother: Self) -> Self: return type(self)(*(gene * mother[sequence] for sequence, gene in enumerate(self)))
5. 同步完善其他方法的类型提示(可选)
为了让整个类的类型检查更完整,同步调整__init__和__add__方法:
def __init__(self, *args: Unpack[T], pure: bool = False) -> None: super().__init__([]) for sequence, GeneType in enumerate(self.GENOME): if sequence < len(args): gene = args[sequence] else: gene = GeneType(pure=pure) if not isinstance(gene, GeneType): raise ValueError(f'Given sequence {args} is not compatible with Genome mapping {", ".join(g.__name__ for g in self.GENOME)}!') self.append(gene) def __add__(self, mother: Self) -> list[Self]: return [type(self)(*genome) for genome in list(itertools.product(*[self[sequence] + mother[sequence] for sequence in range(len(self.GENOME))]))]
完整修改后的Genome类片段
from typing import Generic, TypeVarTuple, Unpack, Self, Type import itertools import random from enum import StrEnum class Allele(StrEnum): dominant: int def __new__(cls, value: str, dominant: int) -> Self: allele = str.__new__(cls, value) allele._value_ = value allele.dominant = dominant return allele def __str__(self) -> str: return self.value def __repr__(self) -> str: return self.__str__() class Gene(tuple[Allele, Allele]): ALLELE: type[Allele] = Allele def __init_subclass__(cls, *args, Allele: type[Allele], **kwargs) -> None: super().__init_subclass__(*args, **kwargs) cls.ALLELE = Allele def __str__(self) -> str: for dominance in sorted([allele.dominant for allele in self.ALLELE], reverse=True): group = set(item for item in self if item.dominant == dominance) if len(group) != 0: return ''.join(sorted(group)) return '' def __repr__(self) -> str: return f'({self[0]}, {self[1]})' def __mul__(self, mother: Self) -> Self: return type(self)(random.choice(list(self)), random.choice(list(mother))) def __add__(self, mother: Self) -> list[Self]: return [type(self)(one, other) for one in self for other in mother] T = TypeVarTuple('T') class Genome(list[Unpack[T]], Generic[Unpack[T]]): GENOME: list[Type[Unpack[T]]] = [] def __init_subclass__(cls, *args, genome: list[Type[Unpack[T]]], **kwargs) -> None: super().__init_subclass__(*args, **kwargs) cls.GENOME = genome def __init__(self, *args: Unpack[T], pure: bool = False) -> None: super().__init__([]) for sequence, GeneType in enumerate(self.GENOME): if sequence < len(args): gene = args[sequence] else: gene = GeneType(pure=pure) if not isinstance(gene, GeneType): raise ValueError(f'Given sequence {args} is not compatible with Genome mapping {", ".join(g.__name__ for g in self.GENOME)}!') self.append(gene) def __str__(self) -> str: if all(issubclass(GENE, Gene) for GENE in self.GENOME): return ''.join(str(gene) for gene in self) return ', '.join(str(gene) for gene in self) def __mul__(self, mother: Self) -> Self: return type(self)(*(gene * mother[sequence] for sequence, gene in enumerate(self))) def __add__(self, mother: Self) -> list[Self]: return [type(self)(*genome) for genome in list(itertools.product(*[self[sequence] + mother[sequence] for sequence in range(len(self.GENOME))]))]
原理说明
- TypeVarTuple与Unpack:支持定义可变长度的类型参数,完美适配
Genome子类中genome参数的任意长度序列类型。 - 泛型Genome类:每个子类(如
Blood、Human)会自动绑定自身genome列表对应的类型,类型检查器能精准识别每个位置的元素类型。 - Self类型:确保
__mul__方法的mother参数与self是同一子类实例,返回值也为该子类实例,同时保证每个序列位置的gene * mother[sequence]操作符合对应类型的__mul__方法签名。
经过上述修改后,类型检查器(如mypy)可正确识别所有类型关系,无需再添加# type: ignore注释。
内容的提问来源于stack exchange,提问作者Nuno
相关产品推荐
相关产品推荐

