如果我有一本包含必需键和任意可选键的字典,我应该如何输入该字典?
示例:
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 中还没有办法做到这一点。我们能得到的最接近的是:
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 是一个函数而不是类。也许将来会改变,也许不会。