Python中如何在实例化时检查泛型指定的类型?
嘿,我完全懂你碰到的这个坑——想在__init__方法里获取泛型类指定的具体类型,但发现__orig_class__在实例化的这个阶段根本访问不到,对吧?确实,这是Python typing系统的一个限制,不止Python 3.10,其他版本也存在这个问题,因为__orig_class__是在实例完全创建之后才会被赋值的属性,所以在__init__里自然拿不到。
那有没有其他办法绕过这个限制呢?这里给你几个可行的方案:
方案1:显式传入泛型类型参数
这是最直接也最稳妥的办法,虽然看起来有点“重复”指定类型,但完全不依赖Python的内部属性,兼容性拉满。你可以修改SimpleBaseProcessor的构造方法,让它接收一个loader_class参数,或者在子类里直接把对应的Loader类传进去:
from typing import Type class SimpleBaseProcessor(BaseProcessor, Generic[T]): def __init__(self, another_param: object, loader_class: Type[T]): # 直接用传入的loader_class实例化 loader = loader_class(options["filename"]) super().__init__(loader, another_param) def _process_element(self, element): return element # 子类调用的时候显式传入对应的Loader类 class LineCountPrefixedProcessor(SimpleBaseProcessor[RawLineLoader]): def __init__(self, another_param, prefix = "{0} | "): super().__init__(another_param, RawLineLoader) self._prefix = prefix self._count = 0 def _process_element(self, element): processed = self._prefix.format(self._count) + element self._count += 1 return processed
这种方式的好处是逻辑清晰,任何人看代码都能明白怎么回事,而且不会因为Python版本或者内部属性的变更出问题。
方案2:用元类提前捕获泛型参数
如果你不想让子类重复传入类型,可以用元类在类定义阶段就把泛型的类型参数保存下来,这样实例化的时候就能直接用了:
from typing import TypeVar, Generic, Type T = TypeVar("T", bound=BaseLoader) class ProcessorMeta(type): def __new__(cls, name, bases, attrs): new_cls = super().__new__(cls, name, bases, attrs) # 尝试获取当前类的泛型参数 if hasattr(new_cls, "__orig_bases__"): for base in new_cls.__orig_bases__: origin = getattr(base, "__origin__", None) if origin is SimpleBaseProcessor: # 提取泛型参数并保存为类属性 new_cls._loader_class = base.__args__[0] break return new_cls class SimpleBaseProcessor(BaseProcessor, Generic[T], metaclass=ProcessorMeta): def __init__(self, another_param: object): # 直接用类层面保存的_loader_class loader = self._loader_class(options["filename"]) super().__init__(loader, another_param) def _process_element(self, element): return element # 子类不需要额外修改,正常定义即可 class LineCountPrefixedProcessor(SimpleBaseProcessor[RawLineLoader]): def __init__(self, another_param, prefix = "{0} | "): super().__init__(another_param) self._prefix = prefix self._count = 0 def _process_element(self, element): processed = self._prefix.format(self._count) + element self._count += 1 return processed
这个方案的优点是子类不需要额外传参,和你原来的写法更接近,但要注意__orig_bases__也是Python的内部属性,虽然大部分版本都支持,但如果未来Python有变动可能会受影响,不过目前来看还是比较稳定的。
方案3:在类实例化后延迟初始化(不太推荐)
还有一种思路是把依赖泛型类型的初始化逻辑放到__init__之后,比如写一个setup方法,等实例创建完成(此时__orig_class__已经存在)再调用:
class SimpleBaseProcessor(BaseProcessor, Generic[T]): def __init__(self, another_param: object): self._another_param = another_param # 先不初始化loader,留到setup里 def setup(self): # 此时__orig_class__已经存在 loader_class = self.__orig_class__.__args__[0] loader = loader_class(options["filename"]) self._loader = loader # 修改process方法,确保先调用setup def process(self, elements: Iterable) -> list[Any]: if not hasattr(self, "_loader"): self.setup() data = self._loader.load() result = [] for element in data: output = self._process_element(element) result.append(output) return result
不过这种方法会增加使用成本,因为用户必须记得调用setup,或者你要在所有可能用到_loader的地方做检查,所以除非万不得已,不太推荐这个方案。
最后再提一下你原来的代码问题:你在SimpleBaseProcessor的__init__里尝试访问self.__orig_class__,但这个属性是Python在实例化完成后才会附加到实例上的,所以在__init__执行时它还不存在,自然会报错。上面的几个方案都能帮你绕过这个限制,你可以根据自己的需求选择最合适的~
备注:内容来源于stack exchange,提问作者KLc3088

