You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在泛型类型参数的类上更Pythonic地调用类方法?

问题描述

假设有如下代码:

from abc import ABC, abstractmethod
from typing import Generic, Optional, Self, Type, TypeVar, get_args

class EntityBase(ABC):
    @classmethod
    @abstractmethod
    def factory(cls) -> Self:
        pass
    
class EntityA(EntityBase):
    @classmethod    
    def factory(cls) -> Self:
        return cls()

class EntityB(EntityBase):
    @classmethod
    def factory(cls) -> Self:
        return cls()

T = TypeVar("T", bound=EntityBase)

class EntityProcessorBase(ABC, Generic[T]):
    def get_all(self) -> Optional[EntityBase]:
        # 为简洁起见,省略了从外部源传入工厂的数据
        return get_args(self.__class__.__orig_bases__[0])[0].factory() # type: ignore

class EntityAProcessor(EntityProcessorBase[EntityA]):
    pass

class EntityBProcessor(EntityProcessorBase[EntityB]):
    pass 

print(EntityAProcessor().get_all())
print(EntityBProcessor().get_all())

依赖__orig_bases__确实是在依赖CPython的实现细节(它首次出现于PEP-560),但暂时没有更好的思路。

即将推出的PEP-695提议在运行时提供__type_params__,该PEP说明:

该元组包含TypeVar、ParamSpec和TypeVarTuple实例。

我不确定如何通过TypeVar获取Type[T]——由于T被绑定到EntityBase,Type[T]肯定会拥有factory类方法。

根据此前StackOverflow的讨论,有几种方式可以重写代码实现相同功能,但我希望利用已有的T而不重复代码。

有没有比上述实现更Pythonic的方式?


更Pythonic的实现方案

依赖__orig_bases__确实不够稳健,这里提供两种更符合Python风格的实现方式,既避免访问内部属性,又能复用泛型参数T:

方案一:子类显式指定实体类(简洁直观)

修改基类,要求子类通过类属性绑定对应的实体类型,代码可读性高,完全不依赖内部实现:

from abc import ABC, abstractmethod
from typing import Generic, Optional, Self, Type, TypeVar

class EntityBase(ABC):
    @classmethod
    @abstractmethod
    def factory(cls) -> Self:
        pass
    
class EntityA(EntityBase):
    @classmethod    
    def factory(cls) -> Self:
        return cls()

class EntityB(EntityBase):
    @classmethod
    def factory(cls) -> Self:
        return cls()

T = TypeVar("T", bound=EntityBase)

class EntityProcessorBase(ABC, Generic[T]):
    entity_class: Type[T]
    
    def get_all(self) -> Optional[T]:
        return self.entity_class.factory()

class EntityAProcessor(EntityProcessorBase[EntityA]):
    entity_class = EntityA

class EntityBProcessor(EntityProcessorBase[EntityB]):
    entity_class = EntityB

print(EntityAProcessor().get_all())
print(EntityBProcessor().get_all())

方案二:利用__init_subclass__自动绑定

通过__init_subclass__钩子在子类继承时自动提取泛型参数,子类无需额外代码,同时避免直接依赖内部属性:

from abc import ABC, abstractmethod
from typing import Generic, Optional, Self, Type, TypeVar, get_args

class EntityBase(ABC):
    @classmethod
    @abstractmethod
    def factory(cls) -> Self:
        pass
    
class EntityA(EntityBase):
    @classmethod    
    def factory(cls) -> Self:
        return cls()

class EntityB(EntityBase):
    @classmethod
    def factory(cls) -> Self:
        return cls()

T = TypeVar("T", bound=EntityBase)

class EntityProcessorBase(ABC, Generic[T]):
    entity_class: Type[T]
    
    def __init_subclass__(cls) -> None:
        # 从泛型基类中提取实体类型
        generic_base = next(b for b in cls.__bases__ if hasattr(b, "__origin__"))
        cls.entity_class = get_args(generic_base)[0]
    
    def get_all(self) -> Optional[T]:
        return self.entity_class.factory()

class EntityAProcessor(EntityProcessorBase[EntityA]):
    pass

class EntityBProcessor(EntityProcessorBase[EntityB]):
    pass

print(EntityAProcessor().get_all())
print(EntityBProcessor().get_all())

关于PEP-695的展望

当PEP-695正式落地后,可使用新泛型语法通过__type_params__直接获取泛型参数,代码会更简洁:

# 假设使用PEP-695的新泛型语法
from abc import ABC, abstractmethod
from typing import Optional, Self, Type

class EntityBase(ABC):
    @classmethod
    @abstractmethod
    def factory(cls) -> Self:
        pass
    
class EntityA(EntityBase):
    @classmethod    
    def factory(cls) -> Self:
        return cls()

class EntityB(EntityBase):
    @classmethod
    def factory(cls) -> Self:
        return cls()

class EntityProcessorBase[T: EntityBase](ABC):
    def get_all(self) -> Optional[T]:
        entity_class = self.__class__.__type_params__[0]
        return entity_class.factory()

class EntityAProcessor(EntityProcessorBase[EntityA]):
    pass

class EntityBProcessor(EntityProcessorBase[EntityB]):
    pass

print(EntityAProcessor().get_all())
print(EntityBProcessor().get_all())

不过目前该语法尚未正式发布,需要等待对应Python版本支持。

内容的提问来源于stack exchange,提问作者Zoltán

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:12:57