为什么
Callable
泛型类型在 PEP 483 中所述的参数中是逆变的,我对该问题的分析是否准确? (帖子底部有分析)
我目前正在研究有关使用注释进行类型分析的主题(MyPy、Pyright...)。我正在阅读相关的PEP 483 – 类型提示理论。
本 PEP 在 专用于_协方差和逆变 的部分中声明了以下内容:
说明(有点违反直觉)逆变行为的最佳示例之一是可调用类型。它在返回类型上是协变的,但在参数上是逆变的。
这就是我想要理解的:为什么可调用对象在参数中是逆变的?
PEP 483 提供以下概念和定义:
类型
subtype
是类型 basetype
的子类型,如果:
subtype
中的每个值也在basetype
的值集中;和basetype
中的每个函数也在subtype
的函数集中。如果
subtype
是 basetype
的子类型,则对于所有此类 GenType
和 GenType[basetype]
,如果 GenType[subtype]
是 subtype
的子类型,则泛型类型构造函数 basetype
被称为 “逆变”。
问题“为什么
Callable
在论证中是逆变的?”可以改写为:
类型
subtype
是类型 basetype
的子类型,为什么 Callable[[basetype], None]
是 Callable[[subtype], None]
的子类型?
根据PEP 483的上述内容,这意味着:
Callable[[basetype], None]
的每个值都将包含在 Callable[[subtype], None]
的值集中。Callable[[subtype], None]
的每个函数也将在Callable[[basetype], None]
的函数集中。回答我的主要问题是回答为什么这两个条件得到验证。
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
类型,Callable[[Manager], None]
不是my_callable
类型,它是 足以反驳系统协方差。Callable[[Employee], None]
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]
的函数也接受 Callback[[basetype], None]
吗? (我假设所有这些都归结为这个问题)。
我假设任何这样的函数都会将一个
subtype
实例传递给它的可调用参数,因此Callable[[basetype], None]
将接受这样的subtype
实例,这将使传递一个Callable[[basetype], None]
OK。
但是,断言诸如
my_func
这样的每个函数也属于 Callable[[Callable[basetype], None], None]
类型的函数集合真的足够吗?
假设您有一个函数
f : Callable[[B], int]
和三个类
class A: pass
class B(A): pass
class C(B): pass
f
可以接受 B
或 C
的实例作为其参数。它不能接受 A
的实例。
现在考虑
def f(g: Callable[[B], int) -> int:
b = B()
return g(b)
你可以传递什么样的函数给
f
?显然, Callable[[B], int]
可以工作,因为它可以将 b
作为参数。但是 Callable[[C], int]
类型之一呢?它不能将b
作为参数,因为C
是B
的子类型。因此,
Callable[[C], int]
不是 Callable[[B], int]
的子类型。
但是,您可以传递
Callable[[A], int]
类型的函数,因为b
不仅是B
的实例,而且还是A
的实例。因此,Callable[[A], int]
是Callable[[B], int]
的子类型,因为B
是A
的子类型。这就是所有的逆变。