【发布时间】:2019-08-21 19:43:49
【问题描述】:
我想知道如何(或目前是否可能)表示函数将返回 mypy 可接受的特定类的子类?
这是一个简单的示例,其中基类Foo 被Bar 和Baz 继承,并且有一个便利函数create() 将返回Foo 的子类(Bar 或Baz ) 取决于指定的参数:
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[...] 类型的数字。
有人对如何以不复杂的方式做到这一点有任何想法吗?
如果没有不复杂的方法来做到这一点,我知道有一些不太理想的方法来规避这个问题:
- 概括返回类型:
def create(kind: str) -> typing.Any:
...
这解决了赋值的类型问题,但很糟糕,因为它减少了函数签名返回的类型信息。
- 忽略错误:
bar: Bar = create('bar') # type: ignore
这会抑制 mypy 错误,但这也不理想。我确实喜欢它更明确地表明bar: Bar = ... 是故意的,而不仅仅是编码错误,但抑制错误仍然不太理想。
- 投射类型:
bar: Bar = typing.cast(Bar, create('bar'))
与前一种情况一样,这种情况的积极方面是它使Foo 返回到Bar 分配更加有意明确。如果无法按照我上面的要求进行操作,这可能是最好的选择。我认为我不喜欢使用它的部分原因是作为包装函数的笨拙(在使用和可读性方面)。可能只是现实,因为类型转换不是语言的一部分 - 例如create('bar') as Bar,或create('bar') astype Bar,或类似的东西。
【问题讨论】:
-
不,我不想做
foo: Foo = create('bar'),因为在任何实际场景中(不是我上面创建的过于简化的场景)我想利用Bar子类中提供的功能存在于父类Foo。
标签: python type-hinting mypy