如何使用类型提示要求键值对,当键具有无效的标识符时?

huangapple go评论108阅读模式
英文:

With type hinting, how do I require a key value pair when the key has an invalid identifier?

问题

使用类型提示,在Python中如何要求一个键值对,其中键是一个无效的标识符?

要求的意思是,数据需要通过类型提示和静态分析工具,比如mypy/pyright + IDES 来进行验证。

背景信息:具有无效Python标识符的键被发送到JSON中。

示例:键名类

必须接受具有键值对要求的字典负载。请参阅以下附加要求。

以下是一个示例负载:

{
  'a': 1, # 必需的键值对,具有有效标识符
  '1req': 'blah', # 必需的键,具有无效标识符
  'someAdditionalProp': [] # 可选的附加属性,值必须是列表
}

对于接受此字典负载的要求如下:

  1. 存在一个有效的标识符 'a',其类型为 int,是必需的。
  2. 存在一个无效的标识符 '1req',其类型为 string,是必需的。
  3. 可以传递任何与上述两者不同的字符串键以及值为列表的附加属性。
  4. 必须包括所有输入类型,因此 TypedDict 不适用,因为它不包括第3条。
  5. 在实际使用中,负载可以包含 n 个无效命名的标识符。
  6. 在实际使用中,还可以有其他已知的键,如 b: float,它们是可选的,并且与第3条的附加属性不同。它们不同,因为值类型不同,而此键是已知的字面值(如 'b'),但第3条键是字符串,但其值未知。

我想要的是一个类或函数签名,可以接受上述负载并满足所有必需和可选的键值对的类型提示要求。对于类或函数的无效输入,应该在mypy/启用类型检查的IDE中引发错误。

一个有效的错误示例是:

Argument of type "tuple[Literal['otherReq'], None]" cannot be assigned to parameter "args" of type "OptionalDictPair" in function "__new__"
"tuple[Literal['otherReq'], None]" is incompatible with "OptionalDictPair"
Tuple entry 2 is incorrect type
Type "None" cannot be assigned to type "list[Unknown]"PylancereportGeneralTypeIssues

一个不符合要求的简单实现如下:

DictType = typing.Mapping[
    str,
    typing.Union[str, int, list]
]

def func_with_type_hinting(arg: DictType):
    pass

func_with_type_hinting(
    {
        'a': 1,
        '1req': 'blah',
        'someAdditionalProp': None
    }
)  # 静态分析应该在这里显示错误,someAdditionalProp 的类型是错误的
英文:

With type hinting, how do I require a key value pair in python, where the key is an invalid identifier?
By required I mean that the data is required by type hinting and static analysis tools like mypy/pyright + IDES.
For context: keys with invalid python identifiers are sent over json.

A dict payload with key value pair requirements must be ingested. See below for the additional requirements.

Here is a sample payload:

{
  'a': 1, # required pair with valid identifier
  '1req': 'blah', #required key with invalid identifier
  'someAdditionalProp': [] # optional additional property, value must be list
}

The requirements for the ingestion of this dict payload are are:

  1. there is a valid identifier 'a', with type int that is required
  2. there is an invalid identifier '1req' with type string that is required
  3. additional properties can be passed in with any string keys different from the above two and value list
  4. all input types must be included so TypedDict will not work because it does not capture item 3
  5. in the real life use case the payload can contain n invalidly named identifiers
  6. in real life there can be other known keys like b: float that are optional and are not the same as item 3 additional properties. They are not the same because the value type is different, and this key is a known literal (like 'b') but item 3 keys are strings but their value is unknown

What I want is a class or function signature that ingests the above payload and meets the above type hinting requirements for all required and optional key value pairs. Errors should be thrown for invalid inputs to the class or function in mypy/an IDE with type checking turned on.

An example of an error that would work is:

Argument of type "tuple[Literal['otherReq'], None]" cannot be assigned to parameter "args" of type "OptionalDictPair" in function "__new__"
  "tuple[Literal['otherReq'], None]" is incompatible with "OptionalDictPair"
    Tuple entry 2 is incorrect type
      Type "None" cannot be assigned to type "list[Unknown]"PylancereportGeneralTypeIssues

A naive implementation that does NOT meet requirements would be:

DictType = typing.Mapping[
    str,
    typing.Union[str, int, list]
]

def func_with_type_hinting(arg: DictType):
    pass

func_with_type_hinting(
    {
        'a': 1,
        '1req': 'blah',
        'someAdditionalProp': None
    }
)  # static analysis should show an error here, someAdditionalProp's type is wrong

答案1

得分: 1

你只需要一个TypedDict,可以在keys不是有效标识符的情况下以"函数式"而不是声明式方式定义它。例如,将无效的部分替换为:

Payload = TypedDict('Payload', {'a': int, '1req': str, 'someAdditionalProp': list[str]})

然后在你的函数参数的类型提示中使用Payload

def func_with_typehinting(d: Payload):

不要有别的内容。

英文:

You just need a TypedDict, which can be defined "functionally" rather than declaratively when the keys are, in fact, not valid identifiers. For example, replace the invalid

class Payload(TypedDict):
    a: int
    1req: str
    someAdditionalProp: list[str]

with

Payload = TypedDict('Payload', {'a': int, '1req': str, 'someAdditionalProp': list[str])

Then use Payload as the type hint for your function's parameter.

def func_with_typehinting(d: Payload):
    ...

答案2

得分: 0

选项1(元组的frozenset)是我最喜欢的选项,符合所有要求。唯一的注意事项是必须按照必须参数然后是可选参数的顺序传递参数。

我知道的选项有:

  1. 使用元组的frozenset来要求必须首先传递必需参数,然后是可选参数。
    这假设不是将字典作为字典实例传递,而是传递了一组键值对的元组的frozenset。
  • frozenset确保不重复
  • 元组定义了唯一的键值对
OptionalDictPair = typing.Tuple[typing_extensions.LiteralString, list]

RequiredDictPairs = typing.Union[
    typing.Tuple[typing_extensions.Literal['a'], int],
    typing.Tuple[typing_extensions.Literal['1req'], int]
]

ReqAndOptionalDictPairs = typing.Union[
    typing.Tuple[typing_extensions.Literal['a'], str],
    typing.Tuple[typing_extensions.Literal['1req'], int],
    OptionalDictPair
]


_T_co = typing.TypeVar("_T_co", covariant=True)

class frozenset_with_length(frozenset[_T_co]):
    def __new__(
            cls,
            required_1: RequiredDictPairs,
            required_2: RequiredDictPairs,
            *args: OptionalDictPair) -> typing.Union[frozenset_with_length[RequiredDictPairs], frozenset_with_length[ReqAndOptionalDictPairs]]:
        req_args = (required_1, required_2)
        if not args:
            return super().__new__(cls, *req_args) # type: ignore
        all_args = tuple(req_args)
        all_args.extend(args)
        return super().__new__(cls, *all_args) # type: ignore

tuple_items = frozenset_with_length(*(
    ('a', 1),
    ('1req', 1),
    ('additionalProExample', []),
))
  • 优点:必需键必须存在,值也会被检查
  • 缺点:必需参数必须首先传递,然后是可选参数
  1. 使用一个函数,并将无效参数值移到kwargs中的union中
def some_fun(*, a: int, *kwargs: typing.Union[list, str]):
  • 优点:可以传递未打包的字典
  • 缺点:对无效的必需键名称没有要求
  1. 使用一个函数,并使用*args来定义无效命名参数的值
def some_fun(a: int, *args: str, *kwargs: list):
  • 优点:在kwargs中不显示必需的参数
  • 缺点:参数必须按特定顺序传递
  • 缺点:缺少必需参数键定义
  • 缺点:调用者可以省略*args
英文:

Option 1 (frozenset of tuples) is my favorite and meets all requirements. The only caviat is that arguments must be passed in required then optional order.

Options that I know are:

  1. Use a frozenset of tuples to require that the required args are passed first, then the optional args.
    This assumes that rather than passing the dict in as a dict instance a frozenset of tuples of key value pairs are passed in instead.
  • the frozenset ensures no duplication
  • the tuples define the unique key value pairs
OptionalDictPair = typing.Tuple[typing_extensions.LiteralString, list]

RequiredDictPairs = typing.Union[
    typing.Tuple[typing_extensions.Literal['a'], int],
    typing.Tuple[typing_extensions.Literal['1req'], int]
]

ReqAndOptionalDictPairs = typing.Union[
    typing.Tuple[typing_extensions.Literal['a'], str],
    typing.Tuple[typing_extensions.Literal['1req'], int],
    OptionalDictPair
]


_T_co = typing.TypeVar("_T_co", covariant=True)

class frozenset_with_length(frozenset[_T_co]):
    def __new__(
            cls,
            required_1: RequiredDictPairs,
            required_2: RequiredDictPairs,
            *args: OptionalDictPair) -> typing.Union[frozenset_with_length[RequiredDictPairs], frozenset_with_length[ReqAndOptionalDictPairs]]:
        req_args = (required_1, required_2)
        if not args:
            return super().__new__(cls, *req_args) # type: ignore
        all_args = tuple(req_args)
        all_args.extend(args)
        return super().__new__(cls, *all_args) # type: ignore

tuple_items = frozenset_with_length(*(
    ('a', 1),
    ('1req', 1),
    ('additionalProExample', []),
))
  • pro: all required keys must be present, values checked too
  • con: required parameters must come first, then optional ones
  1. use a function and move the invalid arg value into a union in kwargs
    def some_fun(*, a: int, *kwargs: typing.Union[list, str]):
  • pro can pass in unpacked dict
  • con: no requirement on invalid required key name
  1. use a function and use *args to define the values for the invalidly named parameter
    def some_fun(a: int, *args: str, *kwargs: list):
  • pro: required args not shown in kwargs
  • con: arguments must be passed in specific order
  • con: missing required arg key definition
  • con: *args could be omitted by the invoker

huangapple
  • 本文由 发表于 2023年6月16日 01:52:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76484316.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定