防止 TypedDict 接受任意参数
Prevent TypedDict from accepting arbitrary parameters
我注意到 TypedDict
似乎允许你向它传递任何参数,这不是很好。
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!
我想这是因为 TypedDict 只能在类型检查器级别工作。
但是,如果我仍然想在运行时防止这种情况发生,那么使用 TypedDict 的替代方法是什么?
当前版本 Python 中的类型安全不是在运行时实现的,而是通过使用 mypy
的静态执行前分析实现的
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也是如此(除了对象是不可变的)。
如果你想在运行时强制类型安全,这必须明确地完成,例如:
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
或定义一个更接近 TypedDict 的 class,但通过运行时类型检查,您可以执行以下操作:
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.")
可以类似于 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.
或类似。
EDITED 包含一个运行时类型检查动态 class ,它很容易被子class.
可以使用 mypy 包。
sudo pip3.9 install mypy
rehash
cat typedDict_ex.py
#!/usr/bin/python3.9
from typing import TypedDict
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!
mypy typedDict_ex.py
typedDict_ex.py:10: error: Extra key "thing" for TypedDict "X"
Found 1 error in 1 file (checked 1 source file)
两者 dataclasses and named tuples 都提供对构造的关键检查。使用您的示例:
from dataclasses import dataclass
from typing import NamedTuple
@dataclass
class X1:
id: int
class X2(NamedTuple):
id: int
X1(id=4) # ok
X2(id=4) # ok
X1(id=4, thing=3)
# TypeError: __init__() got an unexpected keyword argument 'id'
X2(id=4, thing=3)
# TypeError: __init__() got an unexpected keyword argument 'id'
请注意,数据类不会阻止您在构造后分配给“坏”属性。以下代码仍然有效:
x1 = X1(id=4)
x1.thing = 3 # still ok
相比之下,命名元组是不可变的,因此无法在运行时分配给任意属性:
x2 = X2(id=4)
x2.thing = 3
# AttributeError: 'X2' object has no attribute 'thing'
我注意到 TypedDict
似乎允许你向它传递任何参数,这不是很好。
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!
我想这是因为 TypedDict 只能在类型检查器级别工作。
但是,如果我仍然想在运行时防止这种情况发生,那么使用 TypedDict 的替代方法是什么?
当前版本 Python 中的类型安全不是在运行时实现的,而是通过使用 mypy
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也是如此(除了对象是不可变的)。
如果你想在运行时强制类型安全,这必须明确地完成,例如:
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
或定义一个更接近 TypedDict 的 class,但通过运行时类型检查,您可以执行以下操作:
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.")
可以类似于 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.
或类似。
EDITED 包含一个运行时类型检查动态 class ,它很容易被子class.
可以使用 mypy 包。
sudo pip3.9 install mypy
rehash
cat typedDict_ex.py
#!/usr/bin/python3.9
from typing import TypedDict
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!
mypy typedDict_ex.py
typedDict_ex.py:10: error: Extra key "thing" for TypedDict "X"
Found 1 error in 1 file (checked 1 source file)
两者 dataclasses and named tuples 都提供对构造的关键检查。使用您的示例:
from dataclasses import dataclass
from typing import NamedTuple
@dataclass
class X1:
id: int
class X2(NamedTuple):
id: int
X1(id=4) # ok
X2(id=4) # ok
X1(id=4, thing=3)
# TypeError: __init__() got an unexpected keyword argument 'id'
X2(id=4, thing=3)
# TypeError: __init__() got an unexpected keyword argument 'id'
请注意,数据类不会阻止您在构造后分配给“坏”属性。以下代码仍然有效:
x1 = X1(id=4)
x1.thing = 3 # still ok
相比之下,命名元组是不可变的,因此无法在运行时分配给任意属性:
x2 = X2(id=4)
x2.thing = 3
# AttributeError: 'X2' object has no attribute 'thing'