Python typing: Dynamically Create Literal Alias from List of Valid Values

jon*_*ach 8 python python-typing

I have a function which validates its argument to accept only values from a given list of valid options. Typing-wise, I reflect this behavior using a Literal type alias, like so:

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}'
        )
    # ...
Run Code Online (Sandbox Code Playgroud)

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

下面的事情工作:

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
Run Code Online (Sandbox Code Playgroud)

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

小智 35

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

typing.Literal[tuple(VALID_ARGUMENTS)]
Run Code Online (Sandbox Code Playgroud)

  • 来自 mypy 文档:“`Literal` 类型可以包含一个或多个文字 bool、int、strs、bytes 和 enum 值。但是,文字类型不能包含任意表达式:像 `Literal[my_string.trim()]` 这样的类型, `Literal[x > 3]` 或 `Literal[3j + 4]` 都是非法的。” 因此,这是有效的 *python* 语法,但任何类型检查器都无法理解,这完全违背了添加类型提示的初衷。https://mypy.readthedocs.io/en/stable/literal_types.html#limitations (16认同)
  • 绝对不违背目的。首先,打字可以被视为结构化文档。这绝对解决了这个问题。 (7认同)
  • 我无法编辑我的评论,但是当然,我同意如果您将类型提示用于运行时目的而不是静态类型,那么这仍然很有用。然而,我觉得可以公平地假设,在有关 Python 类型的问题的背景下,作者正在寻找一种能够满足静态类型检查器需求的解决方案,除非另有说明。到目前为止,类型提示最常见的用途是静态类型检查。 (3认同)
  • 这还有效吗?我得到““Literal”的类型参数必须为 None、文字值(int、bool、str 或 bytes)或枚举值”[Pylance] (2认同)

use*_*ica 14

反过来,VALID_ARGUMENTSArgument以下位置构建:

Argument = typing.Literal['foo', 'bar']
VALID_ARGUMENTS: typing.Tuple[Argument, ...] = typing.get_args(Argument)
Run Code Online (Sandbox Code Playgroud)

有可能在运行时生成ArgumentVALID_ARGUMENTS,但这样做是静态分析,它的类型是注释的主要用途的情况下不兼容。VALID_ARGUMENTS从构建Argument是要走的路。

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

VALID_ARGUMENTS: typing.List[Argument] = list(typing.get_args(Argument))
Run Code Online (Sandbox Code Playgroud)

  • @N4v:这是同构、任意长度元组的语法。 (13认同)
  • `typing.Tuple[Argument, ...]` 中省略号的用途是什么? (3认同)