【问题标题】:How to make python typing recognize subclasses as valid types when it expects their parent class? [duplicate]python - 如何使python类型在期望其父类时将子类识别为有效类型? [复制]
【发布时间】:2020-06-12 07:54:08
【问题描述】:

这是我需要做的一个最小示例:

from typing import Callable, Any


class Data:
    pass


class SpecificData(Data):
    pass


class Event:
    pass


class SpecificEvent(Event):
    pass


def detect_specific_event(data: SpecificData, other_info: str) -> SpecificEvent:
    return SpecificEvent()


def run_detection(callback: Callable[[Data, Any], Event]) -> None:
    return


run_detection(detect_specific_event)

现在我收到警告:

Expected type '(Data, Any) -> Event', got '(data: SpecificData, other_info: str) -> SpecificEvent' instead 

对我来说,这个警告似乎没有意义,因为 SpecificData 和 SpecificEvent 分别是 Data 和 Event 的子类型,所以一切都应该没问题。有没有办法按照我的预期进行这项工作?我的想法是能够拥有类似的东西:

class OtherSpecificData(Data):
    pass


class OtherSpecificEvent(Event):
    pass


def detect_other_event(data: OtherSpecificData, other_info: str) -> OtherSpecificEvent:
    return OtherSpecificEvent()

run_detection(detect_other_event)

所以run_detection 函数尽可能通用。现在这给出了与上面相同的警告。

【问题讨论】:

  • 这里的问题是你在run_detection 中的参数类型意味着传递的可调用对象应该能够在Data 及其所有子类上工作,但是你传递给它一个可调用对象,说它不能工作在Data 上,它只能在SpecificData 上工作。例如,假设detect_specific_eventSpecificData 中使用了在父类中不可用的属性。但是run_detection 被告知它应该期望的回调不会那样做;它被告知它将适用于Data 及其所有子类。那么为什么它传递的函数需要SpecificData
  • 您似乎希望父类充当其子类的联合,但它不能,因为子类可能具有父类没有的功能。如果run_detection 无论数据类型如何都可以调用回调,那么您要求函数比您想要的更具体(仅在基类上操作)(在任何子类上操作)。就run_detection 而言,类之间的关系是无关紧要的。一个简单的解决方法是只使用您的子类型的Union。否则,请查找“组合优于继承”。
  • @Alkasm 我基本同意,但是......这里的 2 个函数可以工作,考虑到 LSP,如果进一步的数据,没有出现在这里都是特定的数据。但是 OP 的代码只显示连接run_detection 这是他们在这里询问的内容,而不是它如何获取数据。如果接下来只有一个 SpecificData 的生成器启动,那么它就可以工作了。但如果它发出 Data 事件则不会。
  • @JLPeyret 确实我的 cmets 对 OP 打算做什么做出了假设,这可能是真的,也可能不是。但是由于 OP 在最后声明“所以run_detection 尽可能通用”,所以这里要重点强调的是 run_detection 期望基类上的可调用对象是更多限制性的,而不是更笼统。

标签: python class typing callable subtype


【解决方案1】:

参数子类型与返回子类型方向相反。

  • 返回值从被调用者分配给调用者。
  • 参数值从调用者分配给被调用者。

并且赋值应该比变量的预期类型更具体。 例如:

data: Data = SpecificData()  # okay
data: SpecificData = Data()  # not okay

所以你应该这样做:

from typing import Callable, Any


class Data:
    pass


class SpecificData(Data):
    pass


class Event:
    pass


class SpecificEvent(Event):
    pass


def detect_specific_event(data: Data, other_info: str) -> SpecificEvent:
    return SpecificEvent()


def run_detection(callback: Callable[[SpecificData, Any], Event]) -> None:
    return


run_detection(detect_specific_event)

【讨论】:

  • 谢谢,这说明了我收到警告的原因。这是否意味着无法定义run_detection 以便它可以同时接受我的示例中的detect_specific_eventdetect_other_event
  • 我想是的。这两个函数都需要不同的特定类型,因此您无法概括它们。如果需要泛化,我建议将detect_specific_event的参数类型改为Data, Any
  • 嗯,抱歉,对打字了解不多,从概念上讲没有什么意义。从它的名字来看,我预计detect_specific_event 想要一个SpecificData,并且可能会遇到更通用版本的问题。然而,为了让打字愉快,你的函数签名向人类读者传达了完全相反的意思。
【解决方案2】:

我花了一段时间才记住要使用哪一段打字,但恕我直言,您想使用cast

与在其他语言中的用法不同,cast(x,y)任何事情,但它确实告诉键入 将 y 视为类型 x 。运行时,它是无操作的,只返回 y。

就像编译语言一样,如果我阅读它,我会特别注意代码:这真的可以在运行时工作吗?数据类型实际上是正确的吗?

  • 如果你不能保证以后生成数据的东西只会分发SpecificDatas,那么带有 LSP 注释的重复关闭是合适的。如果可以的话,铸造就可以了。您的最小示例缺少该位,但如果您显示了通过print(data) 传递的实际数据,那么我们就知道是否应用了 LSP。
from typing import Callable, Any, cast


class Data:
    pass


class SpecificData(Data):
    pass


class Event:
    pass


class SpecificEvent(Event):
    pass


def detect_specific_event(data: SpecificData, other_info: str) -> SpecificEvent:
    return SpecificEvent()


def run_detection(callback: Callable[[Data, Any], Event]) -> None:
    return


run_detection(cast((Callable[[Data, Any], Event]),detect_specific_event))

在这里,您基本上已经告诉打字,“接受我的话”detect_specific_eventCallable[[Data, Any], Event])

运行和类型检查的输出:

$ mypy test2.py
Success: no issues found in 1 source file
$ python test2.py
(venv)$  ? well your code says nothing.

将演员更改为实际的签名:

run_detection(cast((Callable[[SpecificData, Any], SpecificEvent]),detect_specific_event))

(venv) $@so.mypy$ mypy test2.py
Argument 1 to "run_detection" has incompatible type "Callable[[SpecificData, Any], SpecificEvent]"; expected "Callable[[Data, Any], Event]"
Found 1 error in 1 file (checked 1 source file)
$ python test2.py 
$? well your code says nothing.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-27
    • 2017-11-28
    • 2012-09-16
    • 2013-09-12
    • 1970-01-01
    • 2021-10-18
    相关资源
    最近更新 更多