非特定数据类实例的类型提示
Posted
技术标签:
【中文标题】非特定数据类实例的类型提示【英文标题】:type hint for an instance of a non specific dataclass 【发布时间】:2019-07-07 03:46:48 【问题描述】:我有一个接受任何dataclass
实例的函数。
什么是合适的类型提示?
在 python 文档中没有找到官方的东西
这是我一直在做的,但我认为它不正确
from typing import Any, NewType
DataClass = NewType('DataClass', Any)
def foo(obj: DataClass):
...
另一个想法是使用Protocol
和这些类属性__dataclass_fields__
、__dataclass_params__
。
【问题讨论】:
呃,什么?带有@dataclass
装饰器的类和没有装饰器的类之间没有明显的区别。数据类不实现任何特殊方法,也没有任何特殊属性。区分“数据类”和“常规”类没有任何意义。
该函数将数据类解包到字典中,它们具有特殊属性__dataclass_fields__
、__dataclass_params__
。如问题所述。关于命名元组也可以这样说,并且确实有类型提示,即使它们只是从 tuple
继承
这些属性没有记录,因此我建议不要依赖它们的存在。不过,我认为没有明显的差异是错误的;像dataclasses.astuple
这样的函数只适用于数据类。
所以Protocol
和astuple
方法?听起来不错,但有点不稳定。不知道为什么他们决定用装饰器创建dataclass
es,而不是通过继承和像namedtuple
s 这样的元类。
没错,我看了源码,python实际上实现了一个函数_is_dataclass_instance
。它检查它是否具有属性__dataclass_fields__
,我认为这已经足够了。
【参考方案1】:
可以使用一个名为is_dataclass
的辅助函数,它从dataclasses
导出。
基本上它的作用是这样的:
def is_dataclass(obj):
"""Returns True if obj is a dataclass or an instance of a
dataclass."""
cls = obj if isinstance(obj, type) else type(obj)
return hasattr(cls, _FIELDS)
它使用类型获取实例的类型,或者如果对象扩展类型,则获取对象本身。
然后它检查变量_FIELDS,等于__dataclass_fields__
,是否存在于这个对象上。这基本上等同于这里的其他答案。
要“输入”数据类,我会这样做:
class DataclassProtocol(Protocol):
__dataclass_fields__: Dict
__dataclass_params__: Dict
__post_init__: Optional[Callable]
【讨论】:
【参考方案2】:尽管有它的名字,dataclasses.dataclass
并没有公开类接口。它只是允许您以一种方便的方式声明一个自定义类,这使得它很明显将用作数据容器。因此,理论上,几乎没有机会编写仅适用于数据类的东西,因为数据类实际上只是普通的类。
在实践中,无论如何您都希望声明仅数据类的函数有几个原因,我看到了两种方法。
正确的方式,使用静态类型检查器并编写协议
from dataclasses import dataclass
from typing import Dict
from typing_extensions import Protocol
class IsDataclass(Protocol):
# as already noted in comments, checking for this attribute is currently
# the most reliable way to ascertain that something is a dataclass
__dataclass_fields__: Dict
def dataclass_only(x: IsDataclass):
... # do something that only makes sense with a dataclass
@dataclass
class A:
pass
dataclass_only(A()) # a static type check should show that this line is fine
这种方法也是您在问题中提到的,但它有三个缺点:
您需要一个第三方库,例如mypy
来为您进行静态类型检查
如果您使用的是 python 3.7 或更早版本,您还需要手动安装 typing_extensions
,因为 Protocol
不是其中的核心 typing
模块的一部分
最后但同样重要的是,以这种方式使用数据类协议doesn't work right now
更多EAFP-inspired 确实有效
from dataclasses import is_dataclass
def dataclass_only(x):
"""Do something that only makes sense with a dataclass.
Raises:
ValueError if something that is not a dataclass is passed.
... more documentation ...
"""
if not is_dataclass(x):
raise ValueError(f"'x.__class__.__name__' is not a dataclass!")
...
在这种方法中,由于文档的原因,该代码的维护者或用户仍然非常清楚其行为。但缺点是您无法对代码进行静态分析(包括 IDE 的类型提示),现在和以后都不会。
【讨论】:
是否有可能将从虚拟类型继承的monkeypatch 到@dataclass
注释器上,隐藏在if TYPE_CHECKING
后面,这样它只会影响类型检查? (即IAmADataclass = type('IAmADataclass', (),
,当您使用@dataclass Class Foo
时,它会有效地将其替换为@dataclass Class Foo(IAmADataclass)
@user3534080 这是一个复杂的问题,您设法将其纳入您的评论。它应该有自己的答案,但简短的回答是,出于实际目的,您想要的并不是很有用。在运行时修补继承意味着静态分析工具(如 mypy 或 IDE 用于提供建议的工具)无法正确提取它们。
看起来链接的 github 问题已经解决,这个答案可能需要快速更新
@Gricey 感谢您的提醒。链接的修复计划与 mypy 的 0.920 版本一起发布,该版本尚未发布。不过,一旦出现这种情况,我会密切关注并更新我的帖子。以上是关于非特定数据类实例的类型提示的主要内容,如果未能解决你的问题,请参考以下文章
如何区分 Swift 3.0 中的类(确定它不是结构或枚举)[重复]