【问题标题】:typing: Dynamically Create Literal Alias from List of Valid Values键入:从有效值列表动态创建文字别名
【发布时间】:2021-03-30 15:56:18
【问题描述】:

我有一个函数可以验证它的参数是否只接受给定的有效选项列表中的值。在打字方面,我使用 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 Literal 类型?

以下事情不起作用

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

那么,怎么做呢?还是根本做不到?

【问题讨论】:

  • 你几乎明白了! Literal 接受类型或文字的元组。 ValidArgs = Literal[tuple(VALID_ARGUMENTS)] 可以。但正如已经提到的,它击败了静态类型检查器。

标签: python type-hinting python-typing


【解决方案1】:

反过来,从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))

【讨论】:

  • typing.Tuple[Argument, ...] 中省略号的用途是什么?
  • @N4v:这是同构、任意长度元组的语法。
【解决方案2】:

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

typing.Literal[tuple(VALID_ARGUMENTS)]

【讨论】:

  • 来自 mypy 文档:“Literal 类型可能包含一个或多个文字 bool、int、strs、字节和枚举值。但是,文字类型不能包含任意表达式:Literal[my_string.trim()] 等类型、Literal[x > 3]Literal[3j + 4] 都是非法的。”所以这是有效的 python 语法,但不会被任何类型检查器理解,这完全违背了首先添加类型提示的意义。 mypy.readthedocs.io/en/stable/literal_types.html#limitations
  • 绝对不会辜负目的。首先,打字可以被视为结构化文档。这绝对解决了这个问题。
【解决方案3】:

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

VALID_ARGUMENTS = ['foo', 'bar']

Argument = Literal['1']

Argument.__args__ = tuple(VALID_ARGUMENTS)

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

【讨论】:

  • 由于这对先前定义的静态类型进行了运行时修改,因此它将无法正常工作。 runtime 类型将是Literal['foo', 'bar'],但实际用于验证的static 类型仍然是Literal['1']
  • @MisterMiyagi 我将此解决方案与 FastAPI + Pydantic 一起使用。它适用于现场验证。我只能通过 POST 请求将 foobar 值传递给我的 API,而不是 1 值。
  • @AlexeiMarinichenko 这很好,但不会改变它不会按预期用途工作。该问题已经展示了两种情况,它们也可以在运行时工作,但比摆弄特定于实现的内部结构要强大得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-19
  • 1970-01-01
  • 1970-01-01
  • 2018-01-14
  • 1970-01-01
相关资源
最近更新 更多