'Make all keys in a typed dict not required
I have an existing TypedDict containing multiple entries:
from typing import TypedDict
class Params(TypedDict):
param1:str
param2:str
param3:str
I want to create the exact same TypedDict but with all the keys being optional so that the user can specify only certain parameters. I know I can do something like:
class OptionalParams(TypedDict, total=False):
param1:str
param2:str
param3:str
but the problem with this method is that I have to duplicate the code. Is there a way to inherit from Params by making the keys optional ? I tried to do
class OptionalParams(Params, total=False):
pass
but the linter does not understand that the parameters are optional
Solution 1:[1]
What you ask for is not possible - at least if you use mypy - as you can read in the comments of Why can a Final dictionary not be used as a literal in TypedDict? and on mypy's github: TypedDict keys reuse?. Pycharm seems to have the same limitation, as tested in the two other "Failed attempts" answers to your question.
When trying to run this code:
from typing import TypeDict
params = {"a": str, "b": str}
Params = TypedDict("Params", params)
mypy will give error: TypedDict() expects a dictionary literal as the second argument
, thrown here in the source code.
Solution 2:[2]
No, you cannot do this for the same set of fields. Quoting from 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.
It's possible to construct required and non required keys with a single TypedDict type.
>>> 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
But you have same set of keys(param1
, param2
, param3
) that needs to be mandatory and optional. Hence that has be inherited from TypedDict
separately with total=False
class OptionalParams(TypedDict, total=False):
param1:str
param2:str
param3:str
Solution 3:[3]
Failed attempt
I tried to copy the class using inheritance, then reassigned the __total__
attribute:
class OptionalParams(Params):
pass
OptionalParams.__total__ = False
If you check the implementation of TypeDict
(I am using Python 3.8.12) you will find that total
is only used to set the __total__
attribute, so it should be safe to reassign it. Below you have the relevant code snippet from the typing module.
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
The code doesn't work anyway.
Solution 4:[4]
Failed attempt #2
From the TypedDict
docstring
TypedDict supports two additional equivalent forms:
Point2D = TypedDict('Point2D', x=int, y=int, label=str) Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
It looks like you can use the second to create both Params
and OptionalParams
classes conveniently:
params = {"params1": str, "params2": str, "params3": str}
Params = TypedDict("Params", params)
OptionalParams = TypedDict("OptionalParams", params, total=False)
I have also tried the following:
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__
These should work in principle, however when running the code through Mypy gives error: TypedDict() expects a dictionary literal as the second argument
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | edd313 |
Solution 2 | |
Solution 3 | |
Solution 4 |