涉及Callable时泛型类中类型变量的不变性问题
为什么泛型类E的类型变量T在第一个示例中是不变的?
核心判定规则
泛型类型参数的变异性(协变/逆变/不变),完全由它在类成员中的使用位置决定:
- 协变:仅出现在输出位置(比如方法返回值)——子类泛型可赋值给父类泛型(如
E[D]→E[C]) - 逆变:仅出现在输入位置(比如方法参数)——父类泛型可赋值给子类泛型(如
E[C]→E[D]) - 不变:同时出现在输入和输出位置——两个泛型类型无法互相赋值,否则会破坏类型安全
第一个示例的问题拆解
看第一个E类的定义:
class E[T: C = C]: def g(self, f: Callable[[], T]) -> T: return f()
这里的T同时承担了两个关键角色:
- 输出角色:方法
g的返回类型是T - 输入约束角色:方法
g要求参数f必须返回T类型的值——因为g要直接返回f()的结果,所以f的返回值必须符合T的类型要求
如果强行允许E[D]()赋值给E[C]类型变量,会触发严重的类型安全问题:
x: E[C] = E[D]() # 假设这行合法 # 按照E[C]的类型约定,x.g可以接受任何返回C的函数 x.g(lambda: C()) # 传入一个返回C实例的函数 # 但实际x是E[D]的实例,它的g方法会把返回值当作D类型处理 # 而C并不是D的子类,后续把这个值当作D使用时必然出错
正是因为T同时涉及输入约束和输出,类型检查器必须将T标记为不变,禁止这种不安全的赋值操作。
对比其他示例的差异
第二个示例:Callable自身的协变特性
x = E() # x是E[C] x.g(D) # Callable[[], D]兼容Callable[[], C]
这里是Callable类型自身的协变规则在起作用:Callable[[], D]可以赋值给Callable[[], C],因为D是C的子类,返回的D实例能安全当作C使用。但这只影响Callable的兼容性,不会改变E[T]的变异性。
第三个示例:T仅作为输出
class E[T: C = C]: def g(self, f: Callable[[], None]) -> T: return cast(T, f())
此时T仅出现在方法g的返回值位置,输入参数f不再依赖T,完全满足协变的条件。因此E[D]()可以安全赋值给E[C]类型变量——输出的D实例能被当作C使用,没有任何类型安全风险。
内容的提问来源于stack exchange,提问作者Alek
相关产品推荐
相关产品推荐

