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

Python泛型中能否在一个TypeVar的bound里引用另一个TypeVar?

Python泛型类中关联Factory与实体类型的实现方案

问题描述

我有一个泛型Base类,它会实例化Factory类。Factory类由Base的子类提供给Base,随后Base会将该实例传递给某些子类方法。我希望Base中Factory的类型依赖于一个更早定义的TypeVar,请问在Python中是否有办法实现这一点?几年前的Stack Overflow问答指出无法实现,现在情况是否有变化?如果没有,是否有替代方案(比如使用Protocol)?

我尝试了如下Python代码:

from typing import Generic, TypeVar

T = TypeVar("T")

class Factory(Generic[T]):
    def __init__(self, session: Session) -> None:
        self.session = session
    
    def get(self, id: int) -> T | None:
        return None

    def all(self) -> list[T]:
        return []

class Session:
    def query(self, query: object) -> object:
        pass
    
F = TypeVar("F", bound=Factory[T])

class Base(Generic[T, F]):
    def __init__(self, Factory: type[F]) -> None:
        self.Factory = Factory

    def doSomething(self, factory: F, thing: T) -> None:
        pass

    def run(self, session: Session) -> None:
        factory = self.Factory(session)
        for thing in factory.all():
            self.doSomething(factory, thing)

class User:
    userId: int
    name: str

class UserFactory(Factory[User]):
    def login(self, user: User) -> None:
        self.session.query('whatever')

class UserThing(Base[User, UserFactory]):
    def __init__(self):
        super().__init__(UserFactory)

    def doSomething(self, factory: UserFactory, user: User) -> None:
        factory.login(user)

运行类型检查时出现错误:

main.py:19: error: Type variable "__main__.T" is unbound  [valid-type]
main.py:19: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
main.py:19: note: (Hint: Use "T" in function signature to bind "T" inside a function)
Found 1 error in 1 file (checked 1 source file)

我更熟悉TypeScript,可以轻松实现该需求,参考代码如下:

class Factory<T> {
    constructor(protected session: Session) {}
    get(id: number): T | null {
        return null
    }
    all(): T[] {
        return []
    }
}

interface Session {
    query(query: unknown): unknown
}

abstract class Base<T, F extends Factory<T>> {
    constructor(public readonly Factory: { new (session:Session): F }) {
    }

    abstract doSomething(factory: F, thing: T): void

    public run(session: Session) {
        const factory = new this.Factory(session)
        for (const thing of factory.all()) {
            this.doSomething(factory, thing)
        }
    }
}

interface User {
    userId: number
    name: string
}

class UserFactory extends Factory<User> {
    login(user: User) {
        this.session.query('whatever')
    }
}

class UserThing extends Base<User, UserFactory> {
    constructor() {
        super(UserFactory)
    }
    doSomething(factory: UserFactory, user: User) {
        factory.login(user)
    }
}

解答

当前Python类型系统的限制

截至Python 3.12+以及最新版mypy,仍然不支持在TypeVar的bound约束中引用另一个未绑定的TypeVar(即F = TypeVar("F", bound=Factory[T])这种写法)。T在这里没有被绑定到任何泛型上下文,类型检查器无法推断它的具体类型,所以会抛出"Type variable is unbound"的错误。几年前Stack Overflow上的结论目前仍然有效。

替代方案:使用Protocol约束Factory类型

我们可以通过Protocol来定义Factory必须满足的接口,同时将其与T类型关联,这样既能保持类型安全,又能绕过TypeVar bound的限制。

修改后的Python代码如下:

from typing import Generic, TypeVar, Protocol, Type

T = TypeVar("T")

class Session:
    def query(self, query: object) -> object:
        pass

# 定义Factory的Protocol,明确关联T类型
class FactoryProtocol(Protocol[T]):
    def __init__(self, session: Session) -> None: ...
    def get(self, id: int) -> T | None: ...
    def all(self) -> list[T]: ...

# 基础Factory类,实现Protocol
class Factory(Generic[T], FactoryProtocol[T]):
    def __init__(self, session: Session) -> None:
        self.session = session
    
    def get(self, id: int) -> T | None:
        return None

    def all(self) -> list[T]:
        return []

# 重新定义Base的泛型参数,F约束为FactoryProtocol[T]
F = TypeVar("F", bound=FactoryProtocol[T])

class Base(Generic[T, F]):
    def __init__(self, factory_cls: Type[F]) -> None:
        self.factory_cls = factory_cls

    def doSomething(self, factory: F, thing: T) -> None:
        pass

    def run(self, session: Session) -> None:
        factory = self.factory_cls(session)
        for thing in factory.all():
            self.doSomething(factory, thing)

class User:
    userId: int
    name: str

class UserFactory(Factory[User]):
    def login(self, user: User) -> None:
        self.session.query('whatever')

class UserThing(Base[User, UserFactory]):
    def __init__(self):
        super().__init__(UserFactory)

    def doSomething(self, factory: UserFactory, user: User) -> None:
        factory.login(user)

另一种简化方案:省略F的显式bound

如果不需要严格约束F必须是Factory的子类,也可以直接将Base定义为Generic[T, F],并在方法中利用类型推断来关联F和T的关系。这种写法更简洁,同样能通过类型检查:

from typing import Generic, TypeVar, Type

T = TypeVar("T")
F = TypeVar("F")

class Session:
    def query(self, query: object) -> object:
        pass

class Factory(Generic[T]):
    def __init__(self, session: Session) -> None:
        self.session = session
    
    def get(self, id: int) -> T | None:
        return None

    def all(self) -> list[T]:
        return []

class Base(Generic[T, F]):
    def __init__(self, factory_cls: Type[F]) -> None:
        self.factory_cls = factory_cls

    def doSomething(self, factory: F, thing: T) -> None:
        pass

    def run(self, session: Session) -> None:
        factory = self.factory_cls(session)
        # 类型检查器会推断factory.all()返回的是list[T]
        for thing in factory.all():
            self.doSomething(factory, thing)

# 后续User、UserFactory、UserThing的定义保持不变

以上两种方案都能实现你想要的类型关联效果,同时通过mypy等类型检查工具的验证。

内容的提问来源于stack exchange,提问作者David Loehr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:49:55