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

Python协议泛型的协变与逆变:为何需将不变量设为逆变?

泛型协议的类型变量方差问题详解

1. 核心规则:函数参数是逆变,返回值是协变

先明确最基础的方差逻辑:

  • 协变:如果A是B的子类,那么X[A]可以替代X[B],这对应返回值场景——比如返回Dog的函数可以替换返回Animal的函数(因为Dog是Animal的子类,返回结果能兼容父类型)。
  • 逆变:如果A是B的子类,那么X[B]可以替代X[A],这对应参数场景——比如接受Animal的函数可以替换接受Dog的函数(因为传给它Dog类型的值,Animal类型的参数完全能接收)。

2. 为什么只有参数的协议要求逆变TypeVar?

看你第一个例子:协议MyProtocol[G]的方法只有参数是G。此时协议作为抽象接口,必须满足子类型传递性——也就是如果一个类实现了MyProtocol[MyType],它应该能被当成MyProtocol[MyType的父类型]使用(比如MyType是Literal["literal_1"],父类型是str)。

但你定义的G是默认的不变(invariant) TypeVar(没指定covariant或contravariant),这就违反了规则:

  • 假设ParentType是MyType的父类型,按照逆变要求,MyProtocol[ParentType]的实现应该能接受任意ParentType类型的参数,但你的CustomClass.do_work只接受MyType,无法兼容更宽泛的ParentType,不符合协议的抽象预期。

所以类型检查器要求你把G声明为逆变:

G = TypeVar("G", contravariant=True)

这样就明确告知检查器:这个TypeVar用于参数位置,遵循逆变规则,实现类的参数类型可以是协议参数类型的子类(因为逆变允许X[父类型]兼容X[子类型]的实现)。

3. 为什么加返回值后警告消失?

当你给方法加上-> G的返回值后,G同时出现在逆变的参数位置和协变的返回值位置。此时逆变和协变的要求相互抵消,TypeVar只能是不变的:

  • 如果设为协变,参数位置的逆变要求不满足;
  • 如果设为逆变,返回值位置的协变要求不满足;
    所以类型检查器认为默认的不变G是合法的——此时协议的子类型规则变成:实现类的参数和返回值必须严格匹配G类型,不能做子类型/父类型的替换,完全符合不变TypeVar的逻辑,因此警告消失。

4. 实际场景示例强化理解

只有参数的逆变场景

from typing import TypeVar, Protocol, Literal

T = TypeVar("T", contravariant=True)
Animal = str
Dog = Literal["dog"]  # Dog是Animal的子类

class Feeder(Protocol[T]):
    def feed(self, animal: T) -> None: ...

class DogFeeder:
    def feed(self, animal: Dog) -> None:
        print(f"Feeding {animal}")

# 逆变规则允许DogFeeder作为Feeder[Animal]使用
feeder: Feeder[Animal] = DogFeeder()  # 合法:DogFeeder接受Dog(子类),能兼容Feeder[Animal]接受Animal的要求

同时有参数和返回值的不变场景

class AnimalHandler(Protocol[T]):
    def handle(self, animal: T) -> T: ...

class DogHandler:
    def handle(self, animal: Dog) -> Dog: ...

# 不变规则要求严格匹配
handler: AnimalHandler[Dog] = DogHandler()  # 合法
handler: AnimalHandler[Animal] = DogHandler()  # 不合法:返回值Dog不能直接当成Animal用,参数Dog也无法接受任意Animal

内容的提问来源于stack exchange,提问作者Tiago Lima

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:55:01