我想知道如何(或目前是否可能)表示一个函数将返回mypy可以接受的特定类的子类?
这是一个简单的示例,其中Foo
和Bar
继承了基类Baz
,并且有一个便利函数create()
会返回{{1}的子类}(Foo
或Bar
),取决于指定的参数:
Baz
用mypy检查此代码时,返回以下错误:
错误:分配中的类型不兼容(表达式的类型为“ Foo”,变量的类型为“ Bar”)
有没有办法表明这应该是可接受/允许的。 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')
函数的预期返回不是(或可能不是)create()
的实例,而是它的子类?
我正在寻找类似的东西:
Foo
但是不存在。显然,在这种简单情况下,我可以这样做:
def create(kind: str) -> typing.Subclass[Foo]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
但是我正在寻找一种可以归纳为N个可能的子类的东西,其中N是一个比我想定义为def create(kind: str) -> typing.Union[Bar, Baz]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
类型的数字大的数字。
有人对如何以一种不费吹灰之力的方式有想法吗?
在可能的情况下,没有没有复杂的方法可以做到这一点,我知道有很多不理想的方法可以解决此问题:
typing.Union[...]
这解决了赋值的键入问题,但令人讨厌,因为它减少了函数签名返回的类型信息。
def create(kind: str) -> typing.Any:
...
这可以抑制mypy错误,但这也不理想。我确实喜欢这样做,这使得bar: Bar = create('bar') # type: ignore
是有意的,而不仅仅是编码错误,但抑制该错误仍然不是理想的情况。
bar: Bar = ...
与前面的情况一样,该情况的积极方面是,它使bar: Bar = typing.cast(Bar, create('bar'))
返回到Foo
的分配更加有意地明确了。如果没有办法完成我上面要问的问题,这可能是最好的选择。我认为我不喜欢使用它的一部分是笨拙(在用法和可读性上)作为包装函数。由于类型转换不是语言的一部分,例如Bar
或create('bar') as Bar
或类似的语言,所以这可能只是现实。
答案 0 :(得分:1)
Mypy并没有抱怨您定义函数的方式:该部分实际上是完全正确且无错误的。
相反,它抱怨的是您在最后一行具有的变量赋值中调用的方式:
bar: Bar = create('bar')
由于注释了create(...)
以返回Foo
或foo的任何子类,因此不能保证将其分配给类型为Bar
的变量是安全的。您在此处的选择是删除注释(并接受bar
的类型为Foo
),将函数的输出直接转换为Bar
或完全重新设计代码以避免这个问题。
如果您希望mypy理解当您传递字符串create
时Bar
将特别返回"bar"
,则可以通过组合overloads来将其归类。和Literal 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]()
但就我个人而言,我对过度使用这种模式会保持谨慎-老实说,我认为频繁使用这些类型的恶作剧是一种代码味。此解决方案也不支持对任意数量的子类型进行特殊处理:您必须为每个子类型创建一个重载变体,该变体会变得非常庞大和冗长。
答案 1 :(得分:1)
您可以找到答案 here。基本上,您需要执行以下操作:
class Foo:
pass
class Bar(Foo):
pass
class Baz(Foo):
pass
from typing import TypeVar
U = TypeVar('U', bound=Foo)
def create(kind: str) -> U:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
bar: Bar = create('bar')
答案 2 :(得分:0)
我使用 typing.Type
解决了类似的问题。对于您的情况,我会这样使用它:
class Foo:
pass
class Bar(Foo):
pass
class Baz(Foo):
pass
def create(kind: str) -> typing.Type[Foo]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
这似乎有效,因为 Type
是“协变的”,虽然我不是专家,但上面的链接指向 PEP484 以获取更多详细信息
答案 3 :(得分:-1)
因此,根据进一步的研究(以及此处的其他回答),对我的问题的简短回答似乎是,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接受分配而没有错误。它仍然不是理想的解决方法,但是在所有解决方案中,它提供的功能与我一直在寻找的最接近。