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

type hint for an instance of a non specific dataclass

我有一个函数可以接受任何 dataclass 的实例。 什么是适合它的类型提示?

尚未在 python 文档中找到官方内容


这是我一直在做的,但我认为它不正确

from typing import Any, NewType

DataClass = NewType('DataClass', Any)
def foo(obj: DataClass):
    ...

另一个想法是使用具有这些 class 属性的 Protocol __dataclass_fields____dataclass_params__.

尽管名称如此,dataclasses.dataclass 并未公开 class 接口。它只是允许您以一种方便的方式声明自定义 class ,这使得它很明显将被用作数据容器。因此,理论上,几乎没有机会编写仅适用于 dataclasses 的东西,因为 dataclasses 实际上只是普通的 classes。

在实践中,有几个原因导致您想要声明数据class-only 函数,您应该这样做:

from dataclasses import dataclass
from typing import Dict, 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 Foo:
    pass

class Bar:
    pass

dataclass_only(Foo())  # a static type check should show that this line is fine ..
dataclass_only(Bar())  # .. and this one is not

这个方法也是你在问题中提到的。如果你想这样做,请记住你需要一个第三方库,例如 mypy to do the static type checking for you, and if you are on python 3.7 or earlier, you need to manually install typing_extensions 因为 Protocol3.8.[=19 中才成为标准库的一部分=]


当我第一次写它时,这个 post 也以旧的做事方式为特色,那时候我们不得不在没有类型检查器的情况下凑合。我放弃了,但不建议再处理这种仅运行时失败的功能:

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!")
    ...

可以使用一个名为 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) 

它使用 type 获取实例的类型,或者如果对象扩展了 type,则获取对象本身。

然后检查此对象上是否存在变量 _FIELDS,它等于 __dataclass_fields__。这基本上等同于这里的其他答案。

要“键入”数据类,我会这样做:


class DataclassProtocol(Protocol):
    __dataclass_fields__: Dict
    __dataclass_params__: Dict
    __post_init__: Optional[Callable]