I noticed that TypedDict
seems to let you pass any arguments to it which is not great.
class X(TypedDict):
id: int
obj1 = X(id=4)
print(obj1)
# {'obj1': 1}
obj2 = X(id=4, thing=3)
print(obj2)
# {'obj1': 1, 'thing': 3} # bad!
I guess this is since TypedDict only works at the type checker level.
But if I still wanted to prevent this happening during runtime, what is the alternative to using a TypedDict?
Type safety in current versions of Python is not achieved at runtime, but through the use of a static ahead-of-execution analysis with mypy
This is true also for dataclasses
, which have a similar scope as TypedDict
, with the difference that dataclasses will check for undefined attributes, but it would not really behave like a dict
. This would be true for NamedTuples too (except that the object is immutable).
If you want to enforce type safety at runtime, this must be done explicitly, e.g.:
class Foo:
def __init__(self, *, bar):
if isinstance(bar, int):
self.bar = bar
else:
raise TypeError
Foo(bar=1)
# <__main__.Foo at 0x7f5400f5c730>
Foo(bar="1")
# TypeError
Foo(baz=1)
# TypeError
or defining a class that would be closer to a TypedDict, but with runtime type checking, you could do something like:
class RuntimeTypedDict(dict):
def __init__(self, **kws):
unseen = set(self.__annotations__.keys())
for key, value in kws.items():
# invalid key/value type checks replicated here for performance
if key in self.__annotations__:
if isinstance(value, self.__annotations__[key]):
unseen.remove(key)
else:
raise TypeError("Invalid value type.")
else:
raise TypeError("Invalid key.")
if unseen != set():
raise TypeError("Missing required key.")
super(RuntimeTypedDict, self).__init__(**kws)
def __setitem__(self, key, value):
if key in self.__annotations__:
if isinstance(value, self.__annotations__[key]):
super(RuntimeTypedDict, self).__setitem__(key, value)
else:
raise TypeError("Invalid value type.")
else:
raise TypeError("Invalid key.")
which can be used similarly to TypedDict:
class MyDict(RuntimeTypedDict):
# __annotations__ = {"x": int} # use this on older Python versions
x: int
d = MyDict(x=1)
print(d)
# {'x': 1}
d["x"] = 2
print(d)
# {'x': 2}
d["x"] = 1.1
# TypeError: Invalid value type.
d["y"] = 1
# TypeError: Invalid key.
d = MyDict(x=1.1)
# TypeError: Invalid value type.
d = MyDict(x=1, y=1)
# TypeError: Invalid key.
d = MyDict()
# TypeError: Missing required key.
or similar.
EDITED to include a runtime type checking dynamic class that is easy to subclass.