根据有效值列表动态创建文字别名

2025-02-27 09:07:00
admin
原创
61
摘要:问题描述:我有一个函数,它验证其参数是否仅接受给定有效选项列表中的值。从类型方面来说,我使用Literal类型别名来反映此行为,如下所示:from typing import Literal VALID_ARGUMENTS = ['foo', 'bar'] Argument = Literal['foo...

问题描述:

我有一个函数,它验证其参数是否仅接受给定有效选项列表中的值。从类型方面来说,我使用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如何动态创建Literal 类型?Argument`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

这到底能不能做到?


解决方案 1:

反过来,VALID_ARGUMENTS从以下开始构建Argument

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

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

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

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

这样做也被认为是语义无效的——规范禁止Literal使用动态计算的参数进行参数化。运行时实现根本没有验证这一点所需的信息。VALID_ARGUMENTS从中构建Argument才是可行的方法。

解决方案 2:

如果有人仍在寻找解决这个问题的方法:

typing.Literal[tuple(VALID_ARGUMENTS)]

解决方案 3:

扩展@user2357112 的答案......可以为"foo"和的各个字符串创建变量"bar"

from __future__ import annotations
from typing import get_args, Literal, TypeAlias

T_foo = Literal['foo']
T_bar = Literal['bar']
T_valid_arguments: TypeAlias = T_foo | T_bar

FOO: T_foo = get_args(T_foo)[0]
BAR: T_bar = get_args(T_bar)[0]

VALID_ARGUMENTS = (FOO, BAR)


def func(argument: T_valid_arguments) -> None:
    if argument not in VALID_ARGUMENTS:
        raise ValueError(f"argument must be one of {VALID_ARGUMENTS}")


#mypy checks
func(FOO)  # OK
func('foo')  # OK
func('baz')  # error: Argument 1 to "func" has incompatible type "Literal['baz']"; expected "Literal['foo', 'bar']"  [arg-type]

reveal_type(FOO) # note: Revealed type is "Literal['foo']" 
reveal_type(BAR). # note: Revealed type is "Literal['bar']"
reveal_type(VALID_ARGUMENTS)  # note: Revealed type is "tuple[Literal['foo'], Literal['bar']]"

但是,有人可能会认为,get_args在这种情况下使用是过度的,以避免在代码中输入两次字符串"foo"。(关于:DRY 与 WET)您可以轻松地执行以下操作并获得相同的结果。

from __future__ import annotations
from typing import Literal, TypeAlias

T_foo = Literal['foo']
T_bar = Literal['bar']
T_valid_arguments: TypeAlias = T_foo | T_bar

FOO: T_foo = 'foo'
BAR: T_bar = 'bar'

VALID_ARGUMENTS = (FOO, BAR)

使用字符串作为注释时需要注意Literal。Mypy 会对此发出警告:

FOO = 'foo'

def func(argument: T_valid_arguments) -> None:
    ...

func(FOO) #  error: Argument 1 to "func" has incompatible type "str"; expected "Literal['foo', 'bar']"  [arg-type]

但下面的就没问题。

func('foo')  # OK

解决方案 4:

看来 Python 官方已经意识到这个功能确实很有用,从 3.11 开始我们可以在以下位置使用可变参数Literal

from typing import Literal, Any
from inspect import signature

def foo(snap: int, crackle: str = 'hello', pop: float = 3.14) -> None:
    pass

valid_values = list(signature(foo).parameters)
FooArgname = Literal[*valid_values]
assert FooArgname == Literal['snap', 'crackle', 'pop']

# example usage: making a type for valid kwargs for foo
ValidFooKwargs = dict[FooArgname, Any]

在上述示例中,“动态”定义有效值列表的用途显而易见:我们希望有一个FooArgname与对象(此处foo)对齐的类型。当我们必须“静态”定义此列表时,我们就不是 DRY 并且冒着与目标对象不一致的风险。

Literal[tuple(valid_values)] 上面 Chris Goddard 提到的解决方案执行时没有语法错误,但我的 linter 仍然以红色显示(运行 3.10 时)。

附录

为了完整起见,由于第一条评论引用了我的原始代码,因此它如下:

from typing import Literal
from inspect import signature, Parameter

valid_values = list(signature(Parameter).parameters)

ParamAttribute = Literal[*valid_values]
assert ParamAttribute == Literal['name', 'kind', 'default', 'annotation']

解决方案 5:

一种解决方案可能是使用StrEnum而不是文字,如果需要,可以将其转换为列表:

from enum import StrEnum

class Argument(StrEnum):
    foo = "foo"
    bar = "bar"


VALID_ARGUMENTS = [el.value for el in Argument]


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

解决方案 6:

如果你使用的是 python3.9,那么这个也可以:

internal = "', '".join(key for key in VALID_ARGUMENTS)

Argument =  eval(f"Literal['{internal}']")

del(internal)

解决方案 7:

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

VALID_ARGUMENTS = ['foo', 'bar']

Argument = Literal['1']

Argument.__args__ = tuple(VALID_ARGUMENTS)

print(Argument)
# typing.Literal['foo', 'bar']
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2974  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1836  
  PLM(产品生命周期管理)系统在企业的产品研发、生产与管理过程中扮演着至关重要的角色。然而,在实际运行中,资源冲突是经常会遇到的难题。资源冲突可能导致项目进度延迟、成本增加以及产品质量下降等一系列问题,严重影响企业的效益与竞争力。因此,如何有效应对PLM系统中的资源冲突,成为众多企业关注的焦点。接下来,我们将详细探讨5...
plm项目管理系统   47  
  敏捷项目管理与产品生命周期管理(PLM)的融合,正成为企业在复杂多变的市场环境中提升研发效率、增强竞争力的关键举措。随着技术的飞速发展和市场需求的快速更迭,传统的研发流程面临着诸多挑战,而将敏捷项目管理理念融入PLM,有望在2025年实现研发流程的深度优化,为企业创造更大的价值。理解敏捷项目管理与PLM的核心概念敏捷项...
plm项目   47  
  模块化设计在现代产品开发中扮演着至关重要的角色,它能够提升产品开发效率、降低成本、增强产品的可维护性与可扩展性。而产品生命周期管理(PLM)系统作为整合产品全生命周期信息的关键平台,对模块化设计有着强大的支持能力。随着技术的不断发展,到 2025 年,PLM 系统在支持模块化设计方面将有一系列令人瞩目的技术实践。数字化...
plm软件   48  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用