不需要输入的字典中的所有键

Make all keys in a typed dict not required

我有一个包含多个条目的现有 TypedDict:

from typing import TypedDict
class Params(TypedDict):
    param1:str
    param2:str
    param3:str

我想创建完全相同的 TypedDict,但所有键都是可选的,以便用户只能指定某些参数。我知道我可以做类似的事情:

class OptionalParams(TypedDict, total=False):
    param1:str
    param2:str
    param3:str

但是这种方法的问题是我必须复制代码。有没有办法通过将键设为可选来从 Params 继承?我试过

class OptionalParams(Params, total=False):
    pass

但是 linter 不理解参数是可选的

不,您不能对同一组字段执行此操作。引用自 PEP 589.

The totality flag only applies to items defined in the body of the TypedDict definition. Inherited items won’t be affected, and instead use totality of the TypedDict type where they were defined. This makes it possible to have a combination of required and non-required keys in a single TypedDict type.

可以使用单个 TypedDict 类型构造 required and non required keys

>>> class Point2D(TypedDict, total=False):
...     x: int
...     y: int
...
>>> class Point3D(Point2D):
...     z: int
...
>>> Point3D.__required_keys__ == frozenset({'z'})
True
>>> Point3D.__optional_keys__ == frozenset({'x', 'y'})
True

但是您有相同的键集(param1param2param3)需要强制可选。因此,它已从 TypedDicttotal=False

分别继承
class OptionalParams(TypedDict, total=False):
    param1:str
    param2:str
    param3:str

尝试失败

我尝试 copy the class 使用继承,然后重新分配 __total__ 属性:

class OptionalParams(Params):
    pass

OptionalParams.__total__ = False

如果你查看TypeDict的实现(我用的是Python3.8.12)你会发现total只是用来设置__total__属性,因此重新分配它应该是安全的。下面是打字模块的相关代码片段。

class TypedDict(dict, metaclass=_TypedDictMeta):
    ...

class _TypedDictMeta(type):
    def __new__(cls, name, bases, ns, total=True):
        ...
        if not hasattr(tp_dict, '__total__'):
            tp_dict.__total__ = total
        return tp_dict

代码无论如何都不起作用。

失败的尝试#2

来自 TypedDict 文档字符串

TypedDict supports two additional equivalent forms:

Point2D = TypedDict('Point2D', x=int, y=int, label=str)
Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})

看起来你可以使用第二个来方便地创建 ParamsOptionalParams 类:

params = {"params1": str, "params2": str, "params3": str}

Params = TypedDict("Params", params)
OptionalParams = TypedDict("OptionalParams", params, total=False)

我也试过以下方法:

class Params(TypedDict):
    param1:str
    param2:str
    param3:str

OptionalParams = TypedDict("OptionalParams", Params.__annotations__, total=False)

assert {"params1": str, "params2": str, "params3": str} == Params.__annotations__

这些原则上应该有效,但是当 运行 通过 Mypy 的代码给出 error: TypedDict() expects a dictionary literal as the second argument.

你要求的是不可能的 - 至少如果你使用 mypy - 你可以在 Why can a Final dictionary not be used as a literal in TypedDict? and on mypy's github: TypedDict keys reuse? 的评论中阅读。 Pycharm 似乎具有相同的限制,如您问题的其他两个“失败尝试”答案中所测试的那样。

尝试运行此代码时:

from typing import TypeDict

params = {"a": str, "b": str}
Params = TypedDict("Params", params)

mypy会在源代码中给出error: TypedDict() expects a dictionary literal as the second argument,抛出here