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

Pro*_*o Q 8 python type-hinting mypy

我有以下代码:

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)
Run Code Online (Sandbox Code Playgroud)

既然MyObject继承自object,为什么MyCallableSubclass在每个地方都MyCallable不起作用?

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

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

那么为什么在 Mypy 中使用Callable[[MyObject], int]而不是Callable[[object], int]抛出错误呢?

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

  1. 为什么会这样?
  2. 我如何解决它?

Pro*_*o Q 8

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

这是怎么回事?

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

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

在这里,Manager子类来自Employee. 也就是说,如果某事物期望一个可以接纳经理的功能,那么它可以过度概括并且可以接纳任何员工,因为它肯定会接纳经理。

然而,在我们的例子中,MyObject来自object. 因此,如果某些东西期待一个可以接收对象的函数,那么如果函数被过度指定并且只能接收MyObjects ,那就不好了。

为什么?想象一个名为的类NotMyObject,它继承自object,但不继承自MyObject。如果一个函数应该能够接受任何对象,那么它应该能够同时接受NotMyObjects 和MyObjects。但是,特定函数只能接收MyObjects,因此不适用于这种情况。

我该如何解决?

Mypy 是正确的。您需要将更具体的函数 ( MyCallableSubclass) 作为类型,否则您的代码可能有错误,或者您输入错误。