我对一个类的定义如下:
from numbers import Number
from typing import Dict
from typeguard import typechecked
Data = Dict[str, Number]
@typechecked
class Foo:
def __init__(self, data: Data):
self._data = dict(data)
@property
def data(self) -> Data:
return self._data
我正在使用typeguard
。我的意图是限制可以进入数据字典的类型。显然,typeguard
会检查整个字典是否传递到函数中或从函数返回。如果字典是直接“公开”的,那么检查类型就成了字典的“责任”——显然,这不起作用:
bar = Foo({'x': 2, 'y': 3}) # ok
bar = Foo({'x': 2, 'y': 3, 'z': 'not allowed'}) # error as expected
bar.data['z'] = 'should also be not allowed but still is ...' # no error, but should cause one
PEP 589引入了类型化词典,但用于一组固定的键(类似于其他语言中类似struct
的构造)。相比之下,我需要一个灵活数量的任意键
我最好的坏主意是走“老派”:对dict
进行子分类,重新实现API的每一位,通过这些API,数据可以进入(或离开)字典,并向它们添加类型检查:
@typechecked
class TypedDict(dict): # just a sketch
def __init__(
self,
other: Union[Data, None] = None,
**kwargs: Number,
):
pass # TODO
def __setitem__(self, key: str, value: Number):
pass # TODO
# TODO
是否有一个有效的替代方案不需要“老派”方法
你的问题似乎有几个部分
(1)在运行时创建类型检查字典
As @juanpa.arrivillaga says in the comments,这与类型-检查有关,但似乎与类型-暗示无关。然而,设计自己的自定义类型检查数据结构是相当简单的。您可以使用^{} 这样做:
使用中:
如果你想概括这类事情,你可以这样做:
使用中:
注意,您不需要对传递给
super().__init__()
的字典进行类型检查UserDict.__init__
调用self.__setitem__
,您已经覆盖了它,因此如果您将无效的字典传递给TypeCheckedDict.__init__
,您会发现异常的引发方式与在构造字典后尝试向字典添加无效键或值的方式相同:UserDict
是专门为方便以这种方式进行子类化而设计的,这就是为什么在这个实例中它是比dict
更好的基类如果要向
TypeCheckedDict
添加类型提示,可以这样做:(上文passes MyPy。)
但是,请注意,添加类型提示与此数据结构在运行时的工作方式完全无关
(2)类型提示词典“用于灵活数量的任意键”
我不太清楚这是什么意思,但如果希望MyPy在向字典添加字符串值时引发错误,则只希望有数值,you could do it like this:
如果您想要MyPy静态检查和运行时检查,您最好(在我看来)使用上面的
TypeCheckedDict
类型提示版本:Mypy对我们将抽象类型作为参数传递给
TypeCheckedDict.__init__
不太满意,因此在实例化dict时必须添加一个# type: ignore[misc]
(这对我来说就像是一个Mypy bug。)但是,除此之外,it works fine(有关使用
SupportsFloat
提示数字类型的注意事项,请参见我的previous answer。如果使用Python<;=3.8,则使用typing.Dict
而不是dict
提示类型。)(3)使用
typeguard
由于您使用的是
typeguard
,因此可以稍微简化我的StrNumberDict
类中的逻辑,如下所示:但是,如果您想要一个更通用的
TypeCheckedDict
可以通过任意类型检查进行实例化,我认为没有办法通过typeguard
实现这一点。以下不起作用:还值得注意的是,typeguard是not currently maintained,因此使用该特定库会涉及一定的风险
很好的建议,但我观察到它比所有这些都简单得多@亚历克斯·韦古德(alex waygood)有一些很好的解释,即使像我一样,你发现他的解决方案有点过火,他的答案也更正确
这里的管道要简单得多,但请注意,我并不是说这是一个命令。我的意图是
如果你真的只是在读一篇经过打字检查的口述,那么,@alex waygood将在这里详细介绍,他的解决方案实际上是完整和正确的
如果您对运行时类型检查更感兴趣,在属性或属性分配时,我相信这个解决方案是优越的,因为它实际上可以可靠地发生,对于许多类型,更正确和完整的选项是A,将被复制粘贴到您的所有用例实现中,或者B,转换为元类或装饰器,修改getitem和setitem dunders,成为下面的某种形式
注意,对于B,许多可能的用例具有不同的需求@s-m-e指定了dict,它不能直接用作dict的包装器/装饰器,但可以很容易地进行调整。我更希望您为此调用修饰类/实例上的原始getitem/setitem dunders,而不是直接修改
self.__dict__
或self
集合。我想要的是使用annotations对象进行类型检查的最简单示例。如果编写安装(或重写)getitem/setitem对的类或实例修饰符,并且如果这些Dunder只是读取/修改self.__dict__
,则会中断DICT。如果您遵守此免责声明,并写出super().__getitem__(in_key)
,那么对于继承遍历的常见问题,第一代类(例如,此类装饰器的某些实现仍然失败)和多重继承存在边缘情况。当您解决这个问题时,您几乎肯定在类型上使用了getattrs,并为相关的__getitem__
和__setitem__
方法或属性探索了多个并行路径相关问题 更多 >
编程相关推荐