在Union中处理具有值的参数的Python方法



在下面的代码中,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类型,但我发现代码相当笨拙。有没有更方便/更优雅的方式?特别是有没有一个均值是否要重用我在类型检查中定义的类型(pos_tanchor_tanchor_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:

  • PEP 634——结构模式匹配:规范
  • PEP 635——结构模式匹配:动机与理由
  • PEP636——结构模式匹配:教程

最新更新