键入:从有效值列表动态创建文字别名

typing: Dynamically Create Literal Alias from List of Valid Values

我有一个函数可以验证其参数以仅接受给定有效选项列表中的值。在输入方面,我使用 Literal 类型别名来反映这种行为,如下所示:

from typing import Literal


VALID_ARGUMENTS = ['foo', 'bar']

Argument = Literal['foo', 'bar']


def func(argument: 'Argument') -> None:
    if argument not in VALID_ARGUMENTS:
        raise ValueError(
            f'argument must be one of {VALID_ARGUMENTS}'
        )
    # ...

这违反了 DRY 原则,因为我必须重写我的 Literal 类型定义中的有效参数列表,即使它已经存储在变量 VALID_ARGUMENTS 中。 如何在给定 VALID_ARGUMENTS 变量的情况下动态创建 Argument 文字类型?

以下操作起作用:

from typing import Literal, Union, NewType


Argument = Literal[*VALID_ARGUMENTS]  # SyntaxError: invalid syntax

Argument = Literal[VALID_ARGUMENTS]  # Parameters to generic types must be types

Argument = Literal[Union[VALID_ARGUMENTS]]  # TypeError: Union[arg, ...]: each arg must be a type. Got ['foo', 'bar'].

Argument = NewType(
    'Argument',
    Union[
        Literal[valid_argument]
        for valid_argument in VALID_ARGUMENTS
    ]
)  # Expected type 'Type[_T]', got 'list' instead

那么,如何才能做到呢?还是根本做不到?

反过来,从 Argument:

构建 VALID_ARGUMENTS
Argument = typing.Literal['foo', 'bar']
VALID_ARGUMENTS: typing.Tuple[Argument, ...] = typing.get_args(Argument)

可以在运行时从 VALID_ARGUMENTS 构建 Argument,但这样做与静态分析不兼容,静态分析是类型注释的主要用例。从 Argument 构建 VALID_ARGUMENTS 是必经之路。

我在这里为 VALID_ARGUMENTS 使用了一个元组,但如果出于某种原因你真的更喜欢列表,你可以得到一个:

VALID_ARGUMENTS: typing.List[Argument] = list(typing.get_args(Argument))

这是解决此问题的方法。但是不知道是不是一个好的解决方案。

VALID_ARGUMENTS = ['foo', 'bar']

Argument = Literal['1']

Argument.__args__ = tuple(VALID_ARGUMENTS)

print(Argument)
# typing.Literal['foo', 'bar']

如果有人仍在为此寻找解决方法:

typing.Literal[tuple(VALID_ARGUMENTS)]