非特定数据类实例的类型提示

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 这样的函数只适用于数据类。 所以Protocolastuple 方法?听起来不错,但有点不稳定。不知道为什么他们决定用装饰器创建dataclasses,而不是通过继承和像namedtuples 这样的元类。 没错,我看了源码,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 版本一起发布,该版本尚未发布。不过,一旦出现这种情况,我会密切关注并更新我的帖子。

以上是关于非特定数据类实例的类型提示的主要内容,如果未能解决你的问题,请参考以下文章

将非特定数量的数据集行添加到列表中

在 SQL 语句中,如何选择所有非特定条件的元素?

如何区分 Swift 3.0 中的类(确定它不是结构或枚举)[重复]

java如何判断一个字符串中是不是包含标点符号(任意标点符号,非特定)?

C++模板函数使用

返回非实例化类类型的类型提示[重复]