包含必需和未确定的可选键的 Python 3 字典的类型提示

问题描述 投票:0回答:1

如果我有一本包含必需键和任意可选键的字典,我应该如何输入该字典?

示例:

Dictionary family:
  father: str,
  mother: str,

  # optional key start
  son1: str,
  daughter1: str,
  son2: str,
  daughter2: str,
  # arbitrary many more sons, daughters, grandparents, ...

family
可以包含其中任何一个。

{father: "Bob", mother: "Alice"}

{father: "Bob", mother: "Alice", son1: "Peter"}

{father: "Bob", mother: "Alice", daughter1: "Jane"}

如果我只使用

Dict[str, str]
,用户无法知道
father
mother
是必需的。如果我使用
TypedDict
,则不允许使用其他键;
total=False
NotRequired[str]
仍然需要预先知道所有键。这个怎么输入?


在 TypeScript 中会使用

type family = {
  father: string,
  mother: string,

  [key: string]: string
}

允许任意类型为

string
的附加键。

Python有这个功能吗?

python python-typing typeddict
1个回答
0
投票

目前 Python 中还没有办法做到这一点。我们能得到的最接近的是:

from typing import TypedDict, NotRequired

class Family(TypedDict):
    father: str
    mother: str
    sons: NotRequired[list[str]]
    daughters: NotRequired[list[str]]

如果这不符合您的需求,唯一的其他简单解决方案是将其记录在文档字符串中并希望代码的用户可以阅读。


复杂的解决方案如下所示,可能是最糟糕的,因为它缺乏静态类型检查器的支持。但它在运行时强制执行正确的类型:

from typing import Any
from collections.abc import MutableMapping

class CustomTypedDict[K, V](MutableMapping[K, V]):

    def __init__(self, fields: dict[type[Any], type[Any]], default_key_type: K, default_value_type: V):
        self._fields = fields
        self._values: dict[any, any] = {}
        self._default_key_type, self._default_value_type = default_key_type, default_value_type


    def __setitem__(self, key, value):
        if key in self._fields:
            if not isinstance(value, self._fields[key]):
                raise ValueError(f"The known key {key} accepts only values from the type {self._fields[key]}: {value}")

        if not isinstance(key, self._default_key_type):
            raise ValueError(f"Unkown keys have to be of type {self._default_key_type}: {key}")
        if not isinstance(value, self._default_value_type):
            raise ValueError(f"Values of unknown keys have to be of type {self._default_value_type}: {value}")

        return self._values.__setitem__(key, value)

    def is_complete(self) -> bool:
        if len(self)< len(self._fields):
            return False
        return set(self.keys()).issubset(set(self._fields.keys()))


    def __getitem__(self, item):
        return self._values.__getitem__(item)

    def __delitem__(self, __key):
        return self._values.__delitem__(__key)

    def __len__(self):
        return self._values.__len__()

    def __iter__(self):
        return self._values.__iter__()




class Family(CustomTypedDict[str, str]):
    def __init__(self):
        CustomTypedDict.__init__(self, fields={"father": str, "mother": str}, default_key_type=str, default_value_type=str)

如果您想要快速失败的方法,对于所需的键,您可以在 Family 类中执行类似的操作:

@classmethod
def filled(cls, entries: dict):
    instance = cls()
    for k, v in entries.items():
        instance[k] = v

    if not instance.is_complete():
        raise ValueError("Please provide any required key.")

    return instance

对我来说最直观的方法是做类似

class Family(TypedDict[str, str]):
的事情,但不幸的是这不起作用,因为 TypedDict 是一个函数而不是类。也许将来会改变,也许不会。

© www.soinside.com 2019 - 2024. All rights reserved.