如何让Mypy按预期处理函数中的子类

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

我有以下代码:

from typing import Callable

MyCallable = Callable[[object], int]
MyCallableSubclass = Callable[['MyObject'], int]

def get_id(obj: object) -> int:
    return id(obj)

def get_id_subclass(obj: 'MyObject') -> int:
    return id(obj)

def run_mycallable_function_on_object(obj: object, func: MyCallable) -> int:
    return func(obj)

class MyObject(object):
    '''Object that is a direct subclass of `object`'''
    pass

my_object = MyObject()

# works just fine
run_mycallable_function_on_object(my_object, get_id)

# Does not work (it runs, but Mypy raises the following error:)
# Argument 2 to "run_mycallable_function_on_object" has incompatible type "Callable[[MyObject], int]"; expected "Callable[[object], int]"
run_mycallable_function_on_object(my_object, get_id_subclass)

既然

MyObject
继承自
object
,为什么
MyCallableSubclass
不能在
MyCallable
能发挥作用的所有地方发挥作用呢?

我读了一些关于 Liskov 替换原理 的内容,还查阅了 Mypy 文档 关于协变和逆变的内容。然而,即使在文档本身中,他们也给出了一个非常相似的例子,他们说

Callable
是在参数类型中表现逆变的类型示例,即
Callable[[Employee], int]
Callable[[Manager], int]
的子类型。

那么为什么使用

Callable[[MyObject], int]
而不是
Callable[[object], int]
在 Mypy 中抛出错误?

总的来说,我有两个问题:

  1. 为什么会出现这种情况?
  2. 如何解决?
python python-typing mypy
1个回答
17
投票

当我写这个问题时,我意识到了我的问题的答案,所以我想我仍然会问这个问题并回答它,以节省人们以后遇到类似问题的时间。

发生什么事了?

注意 Mypy 文档中的最后一个示例:

Callable
是在参数类型中表现逆变的类型示例,即
Callable[[Employee], int]
Callable[[Manager], int]
的子类型。

这里,

Manager
Employee
的子类。也就是说,如果某个东西期望有一个可以容纳经理的功能,那么如果它获得的功能过度概括并且可以容纳任何员工,那也没关系,因为它肯定会容纳经理。

但是,在我们的例子中,

MyObject
object
的子类。因此,如果某个东西需要一个可以接受对象的函数,那么如果它得到的函数过度指定并且只能接受
MyObject
s,那就不好了。

为什么?想象一个名为

NotMyObject
的类继承自
object
,但不继承于
MyObject
。如果一个函数应该能够接受任何对象,那么它应该能够同时接受
NotMyObject
MyObject
。但是,specific 函数只能接受
MyObject
,因此它不适用于这种情况。

如何解决?

Mypy 是正确的。您需要使用更具体的函数 (

MyCallableSubclass
) 作为类型,否则您的代码可能存在错误,或者您输入错误。

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