mypy是否具有Subclass-Acceptable Return Type?

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

我想知道如何(或者当前是否可能)表示函数将返回mypy可接受的特定类的子类?

这是一个简单的例子,其中基类FooBarBaz继承,并且有一个便利函数create()将返回Foo的子类(BarBaz),具体取决于指定的参数:

class Foo:
    pass


class Bar(Foo):
    pass


class Baz(Foo):
    pass


def create(kind: str) -> Foo:
    choices = {'bar': Bar, 'baz': Baz}
    return choices[kind]()


bar: Bar = create('bar')

使用mypy检查此代码时,将返回以下错误:

错误:赋值中的类型不兼容(表达式的类型为“Foo”,变量的类型为“Bar”)

有没有办法表明这应该是可以接受/允许的。 create()函数的预期回报不是(或可能不是)Foo的一个实例,而是它的子类?

我在寻找类似的东西:

def create(kind: str) -> typing.Subclass[Foo]:
    choices = {'bar': Bar, 'baz': Baz}
    return choices[kind]()

但那不存在。显然,在这个简单的例子中,我可以这样做:

def create(kind: str) -> typing.Union[Bar, Baz]:
    choices = {'bar': Bar, 'baz': Baz}
    return choices[kind]()

但我正在寻找能够推广到N个可能子类的东西,其中N是一个大于我想要定义为typing.Union[...]类型的数字。

任何人都有任何关于如何以非复杂的方式做到这一点的想法?


在可能的情况下,没有非复杂的方法来做到这一点,我知道一些不太理想的方法来规避问题:

  1. 概括返回类型:
def create(kind: str) -> typing.Any:
    ...

这解决了赋值的输入问题,但是它很糟糕,因为它减少了函数签名返回的类型信息。

  1. 忽略错误:
bar: Bar = create('bar')  # type: ignore

这抑制了mypy错误,但这也不理想。我确实喜欢它更明确地说bar: Bar = ...是故意的而不仅仅是编码错误,但是抑制错误仍然不太理想。

  1. 投射类型:
bar: Bar = typing.cast(Bar, create('bar'))

与之前的情况一样,这一方面的积极方面是它使Foo更有意识地返回到Bar任务。如果没有办法做我上面提到的问题,这可能是最好的选择。我认为我厌恶使用它的一部分是作为包装函数的笨拙(在使用和可读性方面)。可能只是现实,因为类型转换不是语言的一部分 - 例如create('bar') as Bar,或create('bar') astype Bar,或类似的东西。

python typing mypy
2个回答
2
投票

Mypy并没有抱怨你定义你的功能的方式:那个部分实际上是完全没有错误的。

相反,它抱怨你在最后一行的变量赋值中调用函数的方式:

bar: Bar = create('bar')

由于create(...)注释为返回Foo或foo的任何子类,因此将其分配给Bar类型的变量并不保证是安全的。你在这里的选择是删除注释(并接受bar将是Foo类型),直接将函数的输出转换为Bar,或者完全重新设计代码以避免这个问题。


如果你想让mypy理解当你传入字符串create时,Bar将特别返回"bar",你可以通过组合overloadsLiteral types来解决这个问题。例如。你可以这样做:

from typing import overload
from typing_extensions import Literal   # You need to pip-install this package

class Foo: pass
class Bar(Foo): pass
class Baz(Foo): pass

@overload
def create(kind: Literal["bar"]) -> Bar: ...
@overload
def create(kind: Literal["baz"]) -> Baz: ...
def create(kind: str) -> Foo:
    choices = {'bar': Bar, 'baz': Baz}
    return choices[kind]()

但就个人而言,我会对过度使用这种模式持谨慎态度 - 老实说,我认为频繁使用这些类型的恶作剧是一种代码味道。此解决方案也不支持特殊套管任意数量的子类型:您必须为每个子类型创建一个重载变量,这可能会变得非常笨重和冗长。


-2
投票

因此,经过进一步研究(以及此处的其他回复),对我的问题的简短回答似乎是没有mypy / typing中没有直接支持这种情况的功能。

但是,我确实找到了第四个选项/解决方法,我发现比我在问题中包含的前三个解决方法更令人满意。那就是将基类与typing.Any结合起来。这允许更灵活地指定赋值中的类型定义,同时如果未被赋值覆盖,则保留基类的类型提示。解决方法如下所示:

def create(kind: str) -> typing.Union[Foo, typing.Any]:
    choices = {'bar': Bar, 'baz': Baz}
    return choices[kind]()


bar: Bar = create('bar')

结果是mypy接受了赋值而没有错误。它仍然不是一个理想的解决方法,但它提供的所有解决方案最接近我所期望的用途。

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