用 Union 中的值处理参数的 Pythonic 方法

问题描述 投票:0回答:2

在下面的代码中,

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_t
anchor_t
anchor_pos_t
)?

python mypy python-typing
2个回答
1
投票

您可以使用

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))])

这不是最干净的解决方案,但它确实有效。

如果可能的话,我建议对类使用更加面向对象的方法,以消除对联合类型的需要。


1
投票

这尚不适用于任何当前的 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:

© www.soinside.com 2019 - 2024. All rights reserved.