我有以下代码:
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 中抛出错误?
总的来说,我有两个问题:
当我写这个问题时,我意识到了我的问题的答案,所以我想我仍然会问这个问题并回答它,以节省人们以后遇到类似问题的时间。
发生什么事了?
注意 Mypy 文档中的最后一个示例:
是在参数类型中表现逆变的类型示例,即Callable
是Callable[[Employee], int]
的子类型。Callable[[Manager], int]
这里,
Manager
是Employee
的子类。也就是说,如果某个东西期望有一个可以容纳经理的功能,那么如果它获得的功能过度概括并且可以容纳任何员工,那也没关系,因为它肯定会容纳经理。
但是,在我们的例子中,
MyObject
是object
的子类。因此,如果某个东西需要一个可以接受对象的函数,那么如果它得到的函数过度指定并且只能接受MyObject
s,那就不好了。
为什么?想象一个名为
NotMyObject
的类继承自 object
,但不继承于 MyObject
。如果一个函数应该能够接受任何对象,那么它应该能够同时接受 NotMyObject
和 MyObject
。但是,specific 函数只能接受 MyObject
,因此它不适用于这种情况。
如何解决?
Mypy 是正确的。您需要使用更具体的函数 (
MyCallableSubclass
) 作为类型,否则您的代码可能存在错误,或者您输入错误。