如何让Mypy按预期处理函数中的子类
How to make Mypy deal with subclasses in functions as expected
我有以下代码:
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 substitution principle, and also consulted the Mypy docs 协变和逆变的文章。然而,即使在文档本身中,他们也给出了一个非常相似的例子,他们说
Callable
is an example of type that behaves contravariant in types of arguments, namely Callable[[Employee], int]
is a subtype of Callable[[Manager], int]
.
那么为什么使用 Callable[[MyObject], int]
而不是 Callable[[object], int]
在 Mypy 中抛出错误?
总的来说我有两个问题:
- 为什么会这样?
- 我该如何解决?
当我写这个问题时,我意识到了我的问题的答案,所以我想我仍然会问这个问题并回答它,以节省以后有类似问题的人一些时间。
怎么回事?
请注意 Mypy 文档中的最后一个示例:
Callable
is an example of type that behaves contravariant in types of arguments, namely Callable[[Employee], int]
is a subtype of Callable[[Manager], int]
.
这里,Manager
个来自 Employee
的子class。也就是说,如果某个东西期望一个可以吸收经理的功能,那么如果它得到的功能过度概括并且可以吸收任何员工,那也没关系,因为它肯定会吸收经理。
但是,在我们的例子中,MyObject
子 class 来自 object
。所以,如果某些东西期望一个可以接受对象的函数,那么如果它得到的函数 过度指定 并且只能接受 MyObject
s.
为什么?想象一个名为 NotMyObject
的 class 继承自 object
,但不继承自 MyObject
。如果一个函数应该能够接受任何对象,它应该能够接受 NotMyObject
s 和 MyObject
s。但是,specific 函数只能接受 MyObject
s,因此它不适用于这种情况。
我该如何解决?
Mypy 是正确的。您需要将更具体的函数 (MyCallableSubclass
) 作为类型,否则您的代码可能有错误,或者您输入的内容不正确。
我有以下代码:
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 substitution principle, and also consulted the Mypy docs 协变和逆变的文章。然而,即使在文档本身中,他们也给出了一个非常相似的例子,他们说
Callable
is an example of type that behaves contravariant in types of arguments, namelyCallable[[Employee], int]
is a subtype ofCallable[[Manager], int]
.
那么为什么使用 Callable[[MyObject], int]
而不是 Callable[[object], int]
在 Mypy 中抛出错误?
总的来说我有两个问题:
- 为什么会这样?
- 我该如何解决?
当我写这个问题时,我意识到了我的问题的答案,所以我想我仍然会问这个问题并回答它,以节省以后有类似问题的人一些时间。
怎么回事?
请注意 Mypy 文档中的最后一个示例:
Callable
is an example of type that behaves contravariant in types of arguments, namelyCallable[[Employee], int]
is a subtype ofCallable[[Manager], int]
.
这里,Manager
个来自 Employee
的子class。也就是说,如果某个东西期望一个可以吸收经理的功能,那么如果它得到的功能过度概括并且可以吸收任何员工,那也没关系,因为它肯定会吸收经理。
但是,在我们的例子中,MyObject
子 class 来自 object
。所以,如果某些东西期望一个可以接受对象的函数,那么如果它得到的函数 过度指定 并且只能接受 MyObject
s.
为什么?想象一个名为 NotMyObject
的 class 继承自 object
,但不继承自 MyObject
。如果一个函数应该能够接受任何对象,它应该能够接受 NotMyObject
s 和 MyObject
s。但是,specific 函数只能接受 MyObject
s,因此它不适用于这种情况。
我该如何解决?
Mypy 是正确的。您需要将更具体的函数 (MyCallableSubclass
) 作为类型,否则您的代码可能有错误,或者您输入的内容不正确。