为何Callable泛型类型在参数上是逆变的?
根据PEP 483的说明,为何Callable泛型类型在参数上是逆变的?我的相关分析是否准确?(分析内容见文末)
我目前正在研究用于类型分析的注解相关内容(如MyPy、Pyright等),同时阅读了相关的PEP 483 – 类型提示理论。
该PEP在协变与逆变章节中指出:
用于阐释(看似违反直觉的)逆变行为的最佳示例之一就是可调用类型。它在返回类型上是协变的,但在参数上是逆变的。
我试图理解的核心问题是:为何可调用类型在参数上是逆变的?
PEP 483给出了以下概念和定义:
子类型关系
若满足以下条件,则类型subtype是类型basetype的子类型:
subtype的所有值都属于basetype的值集合;且basetype的所有函数都属于subtype的函数集合。
逆变
若subtype是basetype的子类型,对于所有此类subtype和basetype,若泛型类型构造器GenType满足GenType[basetype]是GenType[subtype]的子类型,则称GenType是“逆变”的。
“为何Callable在参数上是逆变的?”这一问题可重新表述为:
当类型subtype是basetype的子类型时,为何Callable[[basetype], None]是Callable[[subtype], None]的子类型?
根据PEP 483的上述内容,这意味着需要满足以下两个条件:
Callable[[basetype], None]的所有值都包含在Callable[[subtype], None]的值集合中。Callable[[subtype], None]的所有函数都包含在Callable[[basetype], None]的函数集合中。
回答我的核心问题就需要解释为何这两个条件成立。
1. Callable[[basetype], None]的所有值是否都包含在Callable[[subtype], None]的值集合中?
由于subtype是basetype的子类型,subtype的所有值也都是basetype类型的值。
因此,任何接受basetype类型参数的可调用对象也能接受subtype类型的参数。
这意味着Callable[[basetype], None]类型的任何值(可调用对象)同时也是Callable[[subtype], None]类型的值。
因此,答案是肯定的:Callable[[basetype], None]的所有值都属于Callable[[subtype], None]的值集合。
注意: 与之相反的情况(即人们可能直觉认为的协变)并不成立。
我们用PEP 483自身的示例来定义如下代码:
class Employee: ... class Manager(Employee): def manage(self): ... def my_callable(m: Manager): m.manage()在此例中,尽管
my_callable是Callable[[Manager], None]类型,但向其传入Employee对象会违反类型安全性,Manager.manage方法的定义也能明显体现这一点。这意味着my_callable虽然是Callable[[Manager], None]类型,但并非Callable[[Employee], None]类型,这足以反驳协变的普遍性。
2. Callable[[subtype], None]的所有函数是否都包含在Callable[[basetype], None]的函数集合中?
我对这个问题的答案不太确定,也不清楚如何解答。
我们定义以下接受可调用对象作为参数的函数:
def my_func(c: Callable[[subtype], None]): ...
任何具有此类签名(Callable[[Callable[[subtype], None]], None])的函数是否属于Callable[[Callable[[basetype], None]], None]类型?
任何接受Callable[[subtype], None]的函数是否也接受Callable[[basetype], None]?(我认为问题可以归结为此)
我推测任何此类函数都会向其可调用参数传入subtype实例,因此Callable[[basetype], None]会接受该subtype实例,这使得传入Callable[[basetype], None]是可行的。
但这是否足以断言所有类似my_func的函数都属于Callable[[Callable[basetype], None], None]类型的函数集合?
内容的提问来源于stack exchange,提问作者vmonteco

