22

我有一个接受 any 实例的函数dataclass。什么是合适的类型提示?

在 python 文档中没有找到正式的东西


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

from typing import Any, NewType

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

另一个想法是使用Protocol带有这些类属性的 a __dataclass_fields____dataclass_params__.

4

2 回答 2

26

尽管它的名字,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模块的一部分
  • 最后但同样重要的是,以这种方式使用数据类协议现在不起作用

受EAFP启发而实际有效的东西

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 的类型提示),现在和以后都不会。

于 2019-03-19T12:14:53.750 回答
3

有一个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]
于 2021-11-25T16:19:40.277 回答