在 Union 中处理带有值的参数的 Pythonic 方法
Pythonic way to handle arguments with values in Union
在下面的代码中,print_pos
接受一个参数,可以是
三种不同的类型。
from typing import List, Tuple, Union
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def print_pos(
pos: Union[
pos_t,
anchor_pos_t,
List[Union[pos_t, anchor_pos_t]]
]
) -> None:
if isinstance(pos, tuple) and isinstance(pos[0], int):
print('xy =', pos)
elif isinstance(pos, tuple) and isinstance(pos[0], tuple):
print('anchor =', pos[0])
print('xy =', pos[1])
elif isinstance(pos, list):
print('[')
for p in pos:
print_pos(p)
print(']')
else:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([
(0, 100),
(('right', 'bottom'), (0, 100))
])
现在,我使用 isinstance
来检查不同的可能性
对于 pos
的类型,但我发现代码相当笨拙。有没有
更多 convenient/elegant 的方法?特别是有一个意思
重用我在类型检查中定义的类型(pos_t
、anchor_t
、anchor_pos_t
)?
您可以使用 typeguard
库在运行时检查变量类型。
这个库主要用于运行时类型验证,而不是条件类型检查,因此需要定义一个额外的 is_type
函数来满足您的需要。
不幸的是,为了防止类型检查器错误,额外的类型转换也是必要的。
from typing import Any, List, Tuple, Union, cast
from typeguard import check_type
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def is_type(value: Any, expected_type: Any) -> bool:
"""
Return whether the given value is of the expected type or not.
"""
try:
check_type('<blank>', value, expected_type)
return True
except TypeError:
return False
def print_pos(
pos: Union[pos_t, anchor_pos_t, List[Union[pos_t, anchor_pos_t]]]
) -> None:
if is_type(pos, pos_t):
pos = cast(pos_t, pos)
print('xy =', pos)
elif is_type(pos, anchor_pos_t):
pos = cast(anchor_pos_t, pos)
print('anchor =', pos[0])
print('xy =', pos[1])
elif is_type(pos, List[Union[pos_t, anchor_pos_t]]):
pos = cast(List[Union[pos_t, anchor_pos_t]], pos)
print('[')
for p in pos:
print_pos(p)
print(']')
else:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([(0, 100), (('right', 'bottom'), (0, 100))])
这不是最干净的解决方案,但它有效。
如果可能的话,我建议使用 类 更面向对象的方法,以消除对联合类型的需求。
这还不适用于任何当前的 python 版本,但 python 3.10(计划于 2021 年 10 月发布)将具有结构模式匹配。
这允许这样的事情,它可能稍微更具可读性:
from typing import List, Tuple, Union
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def print_pos(
pos: Union[
pos_t,
anchor_pos_t,
List[Union[pos_t, anchor_pos_t]]
]
) -> None:
match pos:
# need to match this more specific case first, as tuple((x, y)) matches this as well
case tuple(((a, b), (x, y))):
print('anchor =', (a, b))
print('xy =', (x, y))
case tuple((x, y)):
print('xy =', (x, y))
case list(_):
print('[')
for p in pos:
print_pos(p)
print(']')
case _:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([
(0, 100),
(('right', 'bottom'), (0, 100))
])
这已经适用于 3.10.0a7 预发布版本,尽管还没有 mypy 支持。
结构模式匹配在某种意义上类似于序列解包,如
(a, b), (x, y) = pos
但更强大。
描述结构模式匹配的三个 PEP:
在下面的代码中,print_pos
接受一个参数,可以是
三种不同的类型。
from typing import List, Tuple, Union
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def print_pos(
pos: Union[
pos_t,
anchor_pos_t,
List[Union[pos_t, anchor_pos_t]]
]
) -> None:
if isinstance(pos, tuple) and isinstance(pos[0], int):
print('xy =', pos)
elif isinstance(pos, tuple) and isinstance(pos[0], tuple):
print('anchor =', pos[0])
print('xy =', pos[1])
elif isinstance(pos, list):
print('[')
for p in pos:
print_pos(p)
print(']')
else:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([
(0, 100),
(('right', 'bottom'), (0, 100))
])
现在,我使用 isinstance
来检查不同的可能性
对于 pos
的类型,但我发现代码相当笨拙。有没有
更多 convenient/elegant 的方法?特别是有一个意思
重用我在类型检查中定义的类型(pos_t
、anchor_t
、anchor_pos_t
)?
您可以使用 typeguard
库在运行时检查变量类型。
这个库主要用于运行时类型验证,而不是条件类型检查,因此需要定义一个额外的 is_type
函数来满足您的需要。
不幸的是,为了防止类型检查器错误,额外的类型转换也是必要的。
from typing import Any, List, Tuple, Union, cast
from typeguard import check_type
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def is_type(value: Any, expected_type: Any) -> bool:
"""
Return whether the given value is of the expected type or not.
"""
try:
check_type('<blank>', value, expected_type)
return True
except TypeError:
return False
def print_pos(
pos: Union[pos_t, anchor_pos_t, List[Union[pos_t, anchor_pos_t]]]
) -> None:
if is_type(pos, pos_t):
pos = cast(pos_t, pos)
print('xy =', pos)
elif is_type(pos, anchor_pos_t):
pos = cast(anchor_pos_t, pos)
print('anchor =', pos[0])
print('xy =', pos[1])
elif is_type(pos, List[Union[pos_t, anchor_pos_t]]):
pos = cast(List[Union[pos_t, anchor_pos_t]], pos)
print('[')
for p in pos:
print_pos(p)
print(']')
else:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([(0, 100), (('right', 'bottom'), (0, 100))])
这不是最干净的解决方案,但它有效。
如果可能的话,我建议使用 类 更面向对象的方法,以消除对联合类型的需求。
这还不适用于任何当前的 python 版本,但 python 3.10(计划于 2021 年 10 月发布)将具有结构模式匹配。
这允许这样的事情,它可能稍微更具可读性:
from typing import List, Tuple, Union
pos_t = Tuple[int, int]
anchor_t = Tuple[str, str]
anchor_pos_t = Tuple[anchor_t, pos_t]
def print_pos(
pos: Union[
pos_t,
anchor_pos_t,
List[Union[pos_t, anchor_pos_t]]
]
) -> None:
match pos:
# need to match this more specific case first, as tuple((x, y)) matches this as well
case tuple(((a, b), (x, y))):
print('anchor =', (a, b))
print('xy =', (x, y))
case tuple((x, y)):
print('xy =', (x, y))
case list(_):
print('[')
for p in pos:
print_pos(p)
print(']')
case _:
raise ValueError('invalid pos')
print_pos((0, 100))
print_pos((('right', 'bottom'), (0, 100)))
print_pos([
(0, 100),
(('right', 'bottom'), (0, 100))
])
这已经适用于 3.10.0a7 预发布版本,尽管还没有 mypy 支持。
结构模式匹配在某种意义上类似于序列解包,如
(a, b), (x, y) = pos
但更强大。
描述结构模式匹配的三个 PEP: