为什么 `Callable` 泛型类型在参数中是逆变的?

问题描述 投票:0回答:1

TL;博士:

为什么

Callable
泛型类型在 PEP 483 中所述的参数中是逆变的,我对该问题的分析是否准确? (帖子底部有分析)


背景:

我目前正在研究有关使用注释进行类型分析的主题(MyPy、Pyright...)。我正在阅读相关的PEP 483 – 类型提示理论

本 PEP 在 专用于_协方差和逆变 的部分中声明了以下内容:

说明(有点违反直觉)逆变行为的最佳示例之一是可调用类型。它在返回类型上是协变的,但在参数上是逆变的。

这就是我想要理解的:为什么可调用对象在参数中是逆变的?


更多来自 PEP 483 的内容:

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的上述内容,这意味着:

  1. Callable[[basetype], None]
    的每个值都将包含在
    Callable[[subtype], None]
    的值集中。
  2. 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
方法的定义显而易见。这 意味着尽管是
Callable[[Manager], None]
类型,
my_callable
不是
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]
的函数也接受
Callback[[basetype], None]
吗? (我假设所有这些都归结为这个问题)。

假设任何这样的函数都会将一个

subtype
实例传递给它的可调用参数,因此
Callable[[basetype], None]
将接受这样的
subtype
实例,这将使传递一个
Callable[[basetype], None]
OK。

但是,断言诸如

my_func
这样的每个函数也属于
Callable[[Callable[basetype], None], None]
类型的函数集合真的足够吗?

python contravariance subtype
1个回答
0
投票

假设您有一个函数

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
的子类型。这就是所有的逆变。

© www.soinside.com 2019 - 2024. All rights reserved.