mypy 是否有 Subclass-Acceptable Return 类型?
Does mypy have a Subclass-Acceptable Return Type?
我想知道如何(或者目前是否可能)表达一个函数将 return 是 mypy 可接受的特定 class 的子class?
这是一个简单的例子,其中基础 class Foo
被 Bar
和 Baz
继承,并且有一个方便的函数 create()
将 return Foo
的子 class(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 检查此代码时,出现以下错误 returned:
错误:赋值中的类型不兼容(表达式的类型为 "Foo",变量的类型为 "Bar")
有没有办法表明这应该是 acceptable/allowable。 create()
函数的预期 return 不是(或可能不是)Foo
的实例,而是它的子 class?
我在寻找类似的东西:
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 个可能的子 class 的东西,其中 N 是一个比我想定义为 typing.Union[...]
类型的数字更大的数字。
有人知道如何以简单的方式做到这一点吗?
在可能没有简单方法的情况下,我知道有许多不太理想的方法可以规避这个问题:
- 泛化 return 类型:
def create(kind: str) -> typing.Any:
...
这解决了赋值的类型问题,但令人失望,因为它减少了函数签名的类型信息 return。
- 忽略错误:
bar: Bar = create('bar') # type: ignore
这会抑制 mypy 错误,但这也不理想。我确实喜欢它更明确地表明 bar: Bar = ...
是故意的,而不仅仅是编码错误,但抑制错误仍然不太理想。
- 投射类型:
bar: Bar = typing.cast(Bar, create('bar'))
与前一个案例一样,这个案例的积极方面是它使 Foo
return 到 Bar
的赋值更加明确。如果无法执行我上面的要求,这可能是最好的选择。我认为我厌恶使用它的部分原因是作为包装函数的笨拙(在使用和可读性方面)。可能只是现实,因为类型转换不是语言的一部分 - 例如 create('bar') as Bar
,或 create('bar') astype Bar
,或类似的东西。
Mypy 并没有抱怨您定义函数的方式:那部分实际上完全没有错误。
相反,它是在抱怨你在最后一行的变量赋值中调用你的函数的方式:
bar: Bar = create('bar')
由于 create(...)
被注释为 return a Foo
或 foo 的任何子类,因此不能保证将其分配给类型为 Bar
的变量是安全的。您在这里的选择是删除注释(并接受 bar
类型为 Foo
),直接将函数的输出转换为 Bar
,或者完全重新设计代码以避免这个问题。
如果你想让 mypy 理解当你传入字符串 "bar"
时 create
将 return 特别是 Bar
,你可以通过合并 overloads and 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]()
但就我个人而言,我会对过度使用此模式持谨慎态度 -- 老实说,我认为频繁使用这些类型的类型恶作剧是一种代码味道。此解决方案也不支持对任意数量的子类型进行特殊封装:您必须为每个子类型创建一个重载变体,这可能会变得非常庞大和冗长。
因此,经过进一步研究(以及此处的其他回复),我的问题的简短答案似乎是 mypy/typing 中没有直接支持此案例的功能。
但是,我确实发现第四个 option/workaround 比我在问题中包含的前三个解决方法更令人满意。那就是将基数 class 与 typing.Any
合并。这允许更灵活地指定赋值中的类型定义,同时在不被赋值覆盖的情况下保留基 class 的类型提示。解决方法如下所示:
def create(kind: str) -> typing.Union[Foo, typing.Any]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
bar: Bar = create('bar')
结果是mypy毫无错误地接受了赋值。它仍然不是一个理想的解决方法,但在所有解决方案中,它提供的最接近我正在寻找的所需用法。
我使用 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
是“协变的”,虽然我不是专家,但上面的 link 指向 PEP484 以获取更多详细信息
你可以找到答案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')
我想知道如何(或者目前是否可能)表达一个函数将 return 是 mypy 可接受的特定 class 的子class?
这是一个简单的例子,其中基础 class Foo
被 Bar
和 Baz
继承,并且有一个方便的函数 create()
将 return Foo
的子 class(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 检查此代码时,出现以下错误 returned:
错误:赋值中的类型不兼容(表达式的类型为 "Foo",变量的类型为 "Bar")
有没有办法表明这应该是 acceptable/allowable。 create()
函数的预期 return 不是(或可能不是)Foo
的实例,而是它的子 class?
我在寻找类似的东西:
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 个可能的子 class 的东西,其中 N 是一个比我想定义为 typing.Union[...]
类型的数字更大的数字。
有人知道如何以简单的方式做到这一点吗?
在可能没有简单方法的情况下,我知道有许多不太理想的方法可以规避这个问题:
- 泛化 return 类型:
def create(kind: str) -> typing.Any:
...
这解决了赋值的类型问题,但令人失望,因为它减少了函数签名的类型信息 return。
- 忽略错误:
bar: Bar = create('bar') # type: ignore
这会抑制 mypy 错误,但这也不理想。我确实喜欢它更明确地表明 bar: Bar = ...
是故意的,而不仅仅是编码错误,但抑制错误仍然不太理想。
- 投射类型:
bar: Bar = typing.cast(Bar, create('bar'))
与前一个案例一样,这个案例的积极方面是它使 Foo
return 到 Bar
的赋值更加明确。如果无法执行我上面的要求,这可能是最好的选择。我认为我厌恶使用它的部分原因是作为包装函数的笨拙(在使用和可读性方面)。可能只是现实,因为类型转换不是语言的一部分 - 例如 create('bar') as Bar
,或 create('bar') astype Bar
,或类似的东西。
Mypy 并没有抱怨您定义函数的方式:那部分实际上完全没有错误。
相反,它是在抱怨你在最后一行的变量赋值中调用你的函数的方式:
bar: Bar = create('bar')
由于 create(...)
被注释为 return a Foo
或 foo 的任何子类,因此不能保证将其分配给类型为 Bar
的变量是安全的。您在这里的选择是删除注释(并接受 bar
类型为 Foo
),直接将函数的输出转换为 Bar
,或者完全重新设计代码以避免这个问题。
如果你想让 mypy 理解当你传入字符串 "bar"
时 create
将 return 特别是 Bar
,你可以通过合并 overloads and 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]()
但就我个人而言,我会对过度使用此模式持谨慎态度 -- 老实说,我认为频繁使用这些类型的类型恶作剧是一种代码味道。此解决方案也不支持对任意数量的子类型进行特殊封装:您必须为每个子类型创建一个重载变体,这可能会变得非常庞大和冗长。
因此,经过进一步研究(以及此处的其他回复),我的问题的简短答案似乎是 mypy/typing 中没有直接支持此案例的功能。
但是,我确实发现第四个 option/workaround 比我在问题中包含的前三个解决方法更令人满意。那就是将基数 class 与 typing.Any
合并。这允许更灵活地指定赋值中的类型定义,同时在不被赋值覆盖的情况下保留基 class 的类型提示。解决方法如下所示:
def create(kind: str) -> typing.Union[Foo, typing.Any]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
bar: Bar = create('bar')
结果是mypy毫无错误地接受了赋值。它仍然不是一个理想的解决方法,但在所有解决方案中,它提供的最接近我正在寻找的所需用法。
我使用 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
是“协变的”,虽然我不是专家,但上面的 link 指向 PEP484 以获取更多详细信息
你可以找到答案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')