接受多种类型之一的(同类)序列的函数的类型提示
Type hints for a function that accepts a (homogeneous) sequence of one of several types
我正在尝试为接受序列的函数提供类型提示
使用两个 type
元素之一,我不知道如何让 mypy
快乐。请注意,序列是同质的,这意味着类型不能混合,要么。通常我在 "compatible" 类型时这样做,例如路径 str
或 pathlib.Path
对象,并用 Union
注释就可以了。但是在序列的情况下,Sequence[Union[..]]
(或Union[Sequence[..]]
)似乎不起作用。这是一个最小的工作示例:
from pathlib import Path
from typing import Sequence, Dict, Union
def fn_accepts_dict(adict):
"""Function from an external module that accepts `dict`s."""
for key, val in adict.items():
print(f"{key}, {val}")
def vararg_test(resources: Sequence[Union[str, Dict]]):
"""My function where I want to provide type hints"""
if isinstance(resources[0], str):
resources2 = [{"path": Path(f)} for f in resources]
else:
resources2 = resources
for d in resources2:
fn_accepts_dict(d)
现在,使用上述定义,调用 vararg_test
这两个按预期工作:
l1 = ["foo/bar", "bar/baz"]
l2 = [{"path": Path("foo/bar")}, {"path": Path("bar/baz")}]
但是 运行 mypy
给我以下错误:
type_hints.py:14: error: Argument 1 to "Path" has incompatible type "Union[str, Dict[Any, Any]]"; expected "Union[str, _PathLike[str]]"
type_hints.py:16: error: Incompatible types in assignment (expression has type "Sequence[Union[str, Dict[Any, Any]]]", variable has type "List[Dict[str, Path]]")
Found 2 errors in 1 file (checked 1 source file)
我该如何解决这个问题?
编辑:
为了提供一些背景知识,str
是一条路径,dict
具有与该路径对应的元数据,函数 fn_accepts_dict
将元数据整理到一个元数据对象中。所以他们的逻辑流程是:str -> dict -> fn_accepts_dict
或 dict -> fn_accepts_dict
.
虽然@ShadowRanger 的建议看起来很有希望,但运气不佳。我得到以下提示的相同错误:
def vararg_test2(resources: Union[Sequence[str], Sequence[Dict]]):
... # same implementation as above
mypy
错误:
type_hints.py:24: error: Argument 1 to "Path" has incompatible type "Union[str, Dict[Any, Any]]"; expected "Union[str, _PathLike[str]]"
type_hints.py:26: error: Incompatible types in assignment (expression has type "Union[Sequence[str], Sequence[Dict[Any, Any]]]", variable has type "List[Dict[str, Path]]")
编辑 2:不幸的是,有了所有注释,它看起来更像 C/C++ 而不是 Python;请参阅下面我的 以获得更多 Pythonic 解决方案。
我建议不要根据参数的类型在一个函数中尝试做两件不同的事情。改为定义两个不同的函数,每个函数采用特定类型的序列。
def do_with_strings(resources: Sequence[str]):
do_with_dicts([{"path": Path(f)} for f in resources])
def do_with_dicts(resources: Sequence[dict]):
for d in resources:
fn_accepts_dict(d)
对于您编写的代码,resources
的类型必须是 ShadowRanger 在评论中建议的 Union[Sequence[str],Sequence[dict]]
,因为您假设整个列表与第一个列表具有相同的类型元素.
如果你想保持异构类型,你需要检查每一个元素以确定它需要变成一个dict
:
def vararg_test(resources: Sequence[Union[str, Dict]]):
for f in resources:
if isinstance(f, str):
f = {"path": Path(f)}
fn_accepts_dict(f)
TLDR:mypy
only understands isinstance(v, tp)
关于 v
。它不理解 isinstance(v<expr>, tp)
关于 v
,例如v[0]: str
表示 v: List[str]
.
的类型
mypy
不理解 isinstance(resources[0], str)
在 resources: Sequence[str]
和 resources: Sequence[Dict]
之间分支。这意味着 resources = resources2
推断两者都是 完全相同的 类型,这在分配或使用 resources2
.
时中断
您必须注释 resources2
以防止推断类型相等,并在分支中注释 cast
以将它们标记为此类。
def vararg_test(resources: Union[Sequence[str], Sequence[Dict]]):
"""My function where I want to provide type hints"""
resources2: Sequence[Dict] # fixed type to prevent inferred equality
if isinstance(resources[0], str): # Mypy does not recognise this branch by itself!
# exclude second Union branch
resources = cast(Sequence[str], resources)
resources2 = [{"path": Path(f)} for f in resources]
else:
# exclude first Union branch
resources2 = cast(Sequence[Dict], resources)
for d in resources2:
fn_accepts_dict(d)
正如@MisterMiyagi 的回答所指出的,问题源于 mypy
在 isinstance
中使用表达式时无法推断类型。所以我尝试了另一种实现方式,其中 isinstance
被传递了一个名称。这还有一个好处,即 vararg_test
现在可以接受任何可迭代对象。
def vararg_test(resources: Iterable[Union[str, Dict]]):
"""My function where I want to use type hints"""
resources2: List[Dict] = []
for res in resources:
if isinstance(res, str):
res = {"path": Path(res)}
resources2 += [res]
for d in resources2:
fn_accepts_dict(d)
然而,这仍然需要我们注释 resources2
,但很容易将其重新表述为列表理解,我们不需要任何注释。
def vararg_test3(resources: Iterable[Union[str, Dict]]):
"""My function where I want to use type hints"""
resources2 = [
{"path": Path(res)} if isinstance(res, str) else res
for res in resources
]
for d in resources2:
fn_accepts_dict(d)
我正在尝试为接受序列的函数提供类型提示
使用两个 type
元素之一,我不知道如何让 mypy
快乐。请注意,序列是同质的,这意味着类型不能混合,要么。通常我在 "compatible" 类型时这样做,例如路径 str
或 pathlib.Path
对象,并用 Union
注释就可以了。但是在序列的情况下,Sequence[Union[..]]
(或Union[Sequence[..]]
)似乎不起作用。这是一个最小的工作示例:
from pathlib import Path
from typing import Sequence, Dict, Union
def fn_accepts_dict(adict):
"""Function from an external module that accepts `dict`s."""
for key, val in adict.items():
print(f"{key}, {val}")
def vararg_test(resources: Sequence[Union[str, Dict]]):
"""My function where I want to provide type hints"""
if isinstance(resources[0], str):
resources2 = [{"path": Path(f)} for f in resources]
else:
resources2 = resources
for d in resources2:
fn_accepts_dict(d)
现在,使用上述定义,调用 vararg_test
这两个按预期工作:
l1 = ["foo/bar", "bar/baz"]
l2 = [{"path": Path("foo/bar")}, {"path": Path("bar/baz")}]
但是 运行 mypy
给我以下错误:
type_hints.py:14: error: Argument 1 to "Path" has incompatible type "Union[str, Dict[Any, Any]]"; expected "Union[str, _PathLike[str]]"
type_hints.py:16: error: Incompatible types in assignment (expression has type "Sequence[Union[str, Dict[Any, Any]]]", variable has type "List[Dict[str, Path]]")
Found 2 errors in 1 file (checked 1 source file)
我该如何解决这个问题?
编辑:
为了提供一些背景知识,str
是一条路径,dict
具有与该路径对应的元数据,函数 fn_accepts_dict
将元数据整理到一个元数据对象中。所以他们的逻辑流程是:str -> dict -> fn_accepts_dict
或 dict -> fn_accepts_dict
.
虽然@ShadowRanger 的建议看起来很有希望,但运气不佳。我得到以下提示的相同错误:
def vararg_test2(resources: Union[Sequence[str], Sequence[Dict]]):
... # same implementation as above
mypy
错误:
type_hints.py:24: error: Argument 1 to "Path" has incompatible type "Union[str, Dict[Any, Any]]"; expected "Union[str, _PathLike[str]]"
type_hints.py:26: error: Incompatible types in assignment (expression has type "Union[Sequence[str], Sequence[Dict[Any, Any]]]", variable has type "List[Dict[str, Path]]")
编辑 2:不幸的是,有了所有注释,它看起来更像 C/C++ 而不是 Python;请参阅下面我的
我建议不要根据参数的类型在一个函数中尝试做两件不同的事情。改为定义两个不同的函数,每个函数采用特定类型的序列。
def do_with_strings(resources: Sequence[str]):
do_with_dicts([{"path": Path(f)} for f in resources])
def do_with_dicts(resources: Sequence[dict]):
for d in resources:
fn_accepts_dict(d)
对于您编写的代码,resources
的类型必须是 ShadowRanger 在评论中建议的 Union[Sequence[str],Sequence[dict]]
,因为您假设整个列表与第一个列表具有相同的类型元素.
如果你想保持异构类型,你需要检查每一个元素以确定它需要变成一个dict
:
def vararg_test(resources: Sequence[Union[str, Dict]]):
for f in resources:
if isinstance(f, str):
f = {"path": Path(f)}
fn_accepts_dict(f)
TLDR:mypy
only understands isinstance(v, tp)
关于 v
。它不理解 isinstance(v<expr>, tp)
关于 v
,例如v[0]: str
表示 v: List[str]
.
mypy
不理解 isinstance(resources[0], str)
在 resources: Sequence[str]
和 resources: Sequence[Dict]
之间分支。这意味着 resources = resources2
推断两者都是 完全相同的 类型,这在分配或使用 resources2
.
您必须注释 resources2
以防止推断类型相等,并在分支中注释 cast
以将它们标记为此类。
def vararg_test(resources: Union[Sequence[str], Sequence[Dict]]):
"""My function where I want to provide type hints"""
resources2: Sequence[Dict] # fixed type to prevent inferred equality
if isinstance(resources[0], str): # Mypy does not recognise this branch by itself!
# exclude second Union branch
resources = cast(Sequence[str], resources)
resources2 = [{"path": Path(f)} for f in resources]
else:
# exclude first Union branch
resources2 = cast(Sequence[Dict], resources)
for d in resources2:
fn_accepts_dict(d)
正如@MisterMiyagi 的回答所指出的,问题源于 mypy
在 isinstance
中使用表达式时无法推断类型。所以我尝试了另一种实现方式,其中 isinstance
被传递了一个名称。这还有一个好处,即 vararg_test
现在可以接受任何可迭代对象。
def vararg_test(resources: Iterable[Union[str, Dict]]):
"""My function where I want to use type hints"""
resources2: List[Dict] = []
for res in resources:
if isinstance(res, str):
res = {"path": Path(res)}
resources2 += [res]
for d in resources2:
fn_accepts_dict(d)
然而,这仍然需要我们注释 resources2
,但很容易将其重新表述为列表理解,我们不需要任何注释。
def vararg_test3(resources: Iterable[Union[str, Dict]]):
"""My function where I want to use type hints"""
resources2 = [
{"path": Path(res)} if isinstance(res, str) else res
for res in resources
]
for d in resources2:
fn_accepts_dict(d)