我有一个类
Animal
,其方法 foo
根据布尔参数 inplace
具有不同的返回类型,该布尔参数跟随可选参数 bar
。我想重载该函数,以便在已知 inplace
的值时知道返回类型
这是我的代码:
# main.py
from __future__ import annotations
from typing import Optional, overload, Literal
class Animal:
@overload
def foo(self, bar=..., inplace: Literal[False]=...) -> Animal:
...
@overload
def foo(self, bar=..., inplace: Literal[True]=...) -> None:
...
def foo(
self, bar=None, inplace: bool = False
) -> Optional[Animal]:
...
reveal_type(Animal().foo(bar='a'))
reveal_type(Animal().foo(inplace=True))
reveal_type(Animal().foo(inplace=False))
$ mypy main.py
main.py:8: error: Overloaded function signatures 1 and 2 overlap with incompatible return types
main.py:21: note: Revealed type is 'main.Animal'
main.py:22: note: Revealed type is 'None'
main.py:23: note: Revealed type is 'main.Animal'
Found 1 error in 1 file (checked 1 source file)
https://mypy-play.net/?mypy=latest&python=3.9&gist=49da369f6343543769eed2060fa61639
如何避免第 8 行出现
Overloaded function signatures 1 and 2 overlap with incompatible return types
错误?
这似乎有效:
from __future__ import annotations
from typing import Optional, overload, Literal
class Animal:
# using defaults
@overload
def foo(self, bar=..., inplace: Literal[False]=...) -> Animal: ...
# using inplace = True
# with bar
@overload
def foo(self, bar, inplace: Literal[True]) -> None: ...
# without bar
@overload
def foo(self, *, inplace: Literal[True]) -> None: ...
# with bool
@overload
def foo(self, bar=..., inplace: bool=...) -> Optional[Animal]: ...
def foo(
self, bar=None, inplace = False
):
...
reveal_type(Animal().foo(bar='a'))
reveal_type(Animal().foo(bar='a', inplace=True))
reveal_type(Animal().foo(bar='a', inplace=False))
reveal_type(Animal().foo(inplace=True))
reveal_type(Animal().foo(inplace=False))
reveal_type(Animal().foo())
inplace: bool
reveal_type(Animal().foo(bar='a', inplace=inplace))
reveal_type(Animal().foo(inplace=inplace))
很多超载,但也许这是不可避免的
Mypy 上的以下问题密切相关:https://github.com/python/mypy/issues/5486
# main.py
from __future__ import annotations
from typing import Literal, Optional, overload
class Animal:
@overload
def foo(self, bar=..., inplace: Literal[False] = ...) -> Animal:
...
@overload
def foo(self, bar=..., *, inplace: Literal[True]) -> None:
...
def foo(self, bar=None, inplace: bool = False) -> Optional[Animal]:
...
reveal_type(Animal().foo(bar="a"))
reveal_type(Animal().foo(inplace=True))
reveal_type(Animal().foo(inplace=False))
$ mypy main.py
main.py:21: note: Revealed type is "main.Animal"
main.py:22: note: Revealed type is "None"
main.py:23: note: Revealed type is "main.Animal"
通过在第二个重载的
*,
参数之前添加 inplace
,并删除其默认值,它确实可以按预期工作。这是在此评论上建议的。
但是,如果您尝试使用位置参数调用该函数,这确实会引入一个问题:
reveal_type(Animal().foo("a", True))
main.py:25: error: No overload variant of "foo" of "Animal" matches argument types "str", "bool"
main.py:25: note: Possible overload variants:
main.py:25: note: def foo(self, bar: Any = ..., inplace: Literal[False] = ...) -> Animal
main.py:25: note: def foo(self, bar: Any = ..., *, inplace: Literal[True]) -> None
main.py:25: note: Revealed type is "Any"
Found 1 error in 1 file (checked 1 source file)
另一个评论中提到了这个问题,建议添加另一个重载可以修复它。
尝试:
@overload
def foo(self, inplace: Literal[False]=..., bar=...) -> Animal:
...
@overload
def foo(self, inplace: Literal[True], bar=...,) -> None:
...
def foo(self, inplace=False, bar=None):
...
我更改了参数的顺序,否则第二个重载应该不正确。
假设你有一个函数:
def fct(a: str = "", b: str = "", c: str = "", flag_len: bool = False, d: str = "", e: str = ""):
"""Return list of parameters a, b, c, d, e which are not empty strings"""
lst_non_empty = list(filter(lambda x: len(x) > 0, [a, b, c, d , e]))
if flag_len:
return len(lst_non_empty)
return lst_non_empty
回归
fct(..., flag_len=True, ...) -> int
fct(..., flag_len=False, ...) -> List[str]
以下完整代码示例使用
typing.overload
将其编码到 mypy.txt 中。只需运行 $ mypy demo.py
来测试您的重载定义是否正确。这是通过assert_type
实现的。
demo.py
:
from typing import List, Literal, overload
from typing_extensions import assert_type
@overload
def fct(a: str = ..., b: str = ..., c: str = ..., flag_len: Literal[False] = ..., d: str = ..., e: str = ...) -> List[str]:
...
@overload
def fct(*, a: str = ..., b: str = ..., c: str = ..., flag_len: Literal[True], d: str = ..., e: str = ...) -> int:
...
@overload
def fct(a: str, *, b: str = ..., c: str = ..., flag_len: Literal[True], d: str = ..., e: str = ...) -> int:
...
@overload
def fct(a: str, b: str, *, c: str = ..., flag_len: Literal[True], d: str = ..., e: str = ...) -> int:
...
@overload
def fct(a: str, b: str, c: str, flag_len: Literal[True], d: str = ..., e: str = ...) -> int:
...
def fct(a: str = "", b: str = "", c: str = "", flag_len: bool = False, d: str = "", e: str = ""):
"""
Return list of parameters a, b, c, d, e which are not empty strings.
flag_len returns just the length of the created list. This argument is in the middle, for demonstrating purposes.
I would recommend it to be the first default argument, as this reduces the effort for overload.
"""
lst_non_empty = list(filter(lambda x: len(x) > 0, [a, b, c, d , e]))
if flag_len:
return len(lst_non_empty)
return lst_non_empty
def test_fct():
assert fct("a", "b", "c") == ["a", "b", "c"]
assert fct("a", "b", "c", True) == 3
assert fct("a", "b", "c", e="e", d="d") == ["a", "b", "c", "d", "e"]
assert fct("a", "b", "c", True, e="e", d="d") == 5
# CHECK RETURN TYPE OF FCT VIA MYPY
# $ mypy filename.py
assert_type(fct(), List[str])
assert_type(fct("a"), List[str])
assert_type(fct("a", "b"), List[str])
assert_type(fct("a", "b", "c"), List[str])
assert_type(fct("a", "b", "c", d="d"), List[str])
assert_type(fct("a", "b", "c", e="d"), List[str])
assert_type(fct("a", "b", "c", e="d", d="d"), List[str])
assert_type(fct("a", "b", e="d", d="d"), List[str])
assert_type(fct("a", e="d", d="d"), List[str])
assert_type(fct(flag_len=False), List[str])
assert_type(fct("a", flag_len=False), List[str])
assert_type(fct("a", "b", flag_len=False), List[str])
assert_type(fct("a", "b", "c", False), List[str])
assert_type(fct("a", "b", "c", flag_len=False), List[str])
assert_type(fct("a", "b", "c", False, d="d"), List[str])
assert_type(fct("a", "b", "c", flag_len=False, d="d"), List[str])
assert_type(fct("a", "b", "c", e="d", flag_len=False), List[str])
assert_type(fct("a", "b", "c", e="d", d="d", flag_len=False), List[str])
assert_type(fct("a", "b", flag_len=False, e="d", d="d"), List[str])
assert_type(fct("a", e="d", flag_len=False, d="d"), List[str])
assert_type(fct(flag_len=True), int)
assert_type(fct("a", flag_len=True), int)
assert_type(fct("a", "b", flag_len=True), int)
assert_type(fct("a", "b", "c", True), int)
assert_type(fct("a", "b", "c", flag_len=True), int)
assert_type(fct("a", "b", "c", True, d="d"), int)
assert_type(fct("a", "b", "c", flag_len=True, d="d"), int)
assert_type(fct("a", "b", "c", e="d", flag_len=True), int)
assert_type(fct("a", "b", "c", e="d", d="d", flag_len=True), int)
assert_type(fct("a", "b", flag_len=True, e="d", d="d"), int)
assert_type(fct("a", e="d", flag_len=True, d="d"), int)
重载中的
...
告诉 mypy 该参数可能存在或不存在。
重载中的 *
告诉 mypy 后面的所有内容都是关键字参数。