什么是数据类,它们与普通类有何不同?

Posted

技术标签:

【中文标题】什么是数据类,它们与普通类有何不同?【英文标题】:What are data classes and how are they different from common classes? 【发布时间】:2021-05-21 08:22:34 【问题描述】:

随着PEP 557 数据类被引入python 标准库。

它们使用@dataclass 装饰器,它们应该是“具有默认值的可变命名元组”,但我不确定我理解这实际上意味着什么以及它们与普通类有何不同。

什么是 python 数据类,什么时候最好使用它们?

【问题讨论】:

鉴于 PEP 的广泛内容,您还想知道什么? namedtuples 是不可变的,不能有属性的默认值,而数据类是可变的,可以有它们。 @jonrsharpe 在我看来应该有一个关于该主题的 *** 线程是合理的。 *** 旨在成为问答格式的百科全书,不是吗?答案绝不是“只看这个其他网站”。这里不应该被否决。 关于如何将项目附加到列表中有五个线程。 @dataclass 上的一个问题不会导致网站崩溃。 @jonrsharpe namedtuples CAN 有默认值。看看这里:***.com/questions/11351032/… 【参考方案1】:

数据类只是用于存储状态的常规类,而不是包含大量逻辑。每次你创建一个主要由属性组成的类时,你就创建了一个数据类。

dataclasses 模块的作用是让创建数据类更容易。它会为您处理大量样板文件。

当您的数据类必须是可散列的时,这尤其有用;因为这需要__hash__ 方法和__eq__ 方法。如果您添加一个自定义的__repr__ 方法以便于调试,那可能会变得非常冗长:

class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def __init__(
            self, 
            name: str, 
            unit_price: float,
            quantity_on_hand: int = 0
        ) -> None:
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand
    
    def __repr__(self) -> str:
        return (
            'InventoryItem('
            f'name=self.name!r, unit_price=self.unit_price!r, '
            f'quantity_on_hand=self.quantity_on_hand!r)'

    def __hash__(self) -> int:
        return hash((self.name, self.unit_price, self.quantity_on_hand))

    def __eq__(self, other) -> bool:
        if not isinstance(other, InventoryItem):
            return NotImplemented
        return (
            (self.name, self.unit_price, self.quantity_on_hand) == 
            (other.name, other.unit_price, other.quantity_on_hand))

使用dataclasses,您可以将其简化为:

from dataclasses import dataclass

@dataclass(unsafe_hash=True)
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

同一个类装饰器也可以生成比较方法(__lt____gt__等)并处理不可变性。

namedtuple 类也是数据类,但默认情况下是不可变的(以及序列)。 dataclasses 在这方面要灵活得多,并且可以很容易地构造成它们可以fill the same role as a namedtuple class。

PEP 的灵感来自 attrs project,它可以做更多事情(包括插槽、验证器、转换器、元数据等)。

如果你想看一些例子,我最近在我的几个Advent of Code 解决方案中使用了dataclasses,请参阅day 7、day 8、day 11 和day 20 的解决方案。

如果你想在 Python 版本 dataclasses 模块,那么你可以安装 backported module(需要 3.6)或使用上面提到的 attrs 项目。

【讨论】:

在第一个示例中,您是否有意隐藏具有同名实例成员的类成员?请帮助理解这个成语。 @VladimirLenin:没有类属性,只有类型注释。请参阅PEP 526,特别是Class and instance variable annotations section。 @Bananach:@dataclass 生成大致相同的__init__ 方法,带有默认值的quantity_on_hand 关键字参数。当您创建实例时,它会始终设置quantity_on_hand 实例属性。所以我的 first,非数据类示例使用相同的模式来回显数据类生成的代码将执行的操作。 @Bananach:所以在第一个示例中,我们可以只是省略设置实例属性而不隐藏类属性,从这个意义上说,无论如何设置它都是多余的,但是数据类设置它。 @user2853437 数据类并不真正支持您的用例;也许你最好使用数据类的大表亲attrs。该项目支持每个字段converters,让您标准化字段值。如果您想坚持使用数据类,那么可以,在 __post_init__ 方法中进行规范化。【参考方案2】:

概述

问题已解决。但是,此答案添加了一些实际示例,以帮助对数据类进行基本理解。

什么是 python 数据类,什么时候最好使用它们?

    代码生成器:生成样板代码;您可以选择在常规类中实现特殊方法或让数据类自动实现它们。 数据容器:保存数据的结构(例如元组和字典),通常带有点属性访问,例如classes, namedtuple and others。

“具有默认值[s]的可变命名元组”

后一句的意思如下:

可变:默认情况下,数据类属性可以重新分配。您可以选择使它们不可变(参见下面的示例)。 namedtuple:你有类似namedtuple 或普通类的点属性访问。 default:您可以为属性分配默认值。

与普通类相比,您主要节省了键入样板代码。


特点

这是对数据类功能的概述(TL;DR?请参阅下一节中的汇总表)。

你会得到什么

以下是您默认从数据类中获得的功能。

属性+表示+比较

import dataclasses


@dataclasses.dataclass
#@dataclasses.dataclass()                                       # alternative
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

这些默认值是通过自动将以下关键字设置为 True 来提供的:

@dataclasses.dataclass(init=True, repr=True, eq=True)

你可以打开什么

如果将适当的关键字设置为True,则可以使用其他功能。

顺序

@dataclasses.dataclass(order=True)
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

现在实现了排序方法(重载运算符:< > <= >=),类似于具有更强相等性测试的functools.total_ordering

可散列、可变

@dataclasses.dataclass(unsafe_hash=True)                        # override base `__hash__`
class Color:
    ...

虽然对象可能是可变的(可能是不希望的),但实现了哈希。

可散列,不可变

@dataclasses.dataclass(frozen=True)                             # `eq=True` (default) to be immutable 
class Color:
    ...

现在实现了哈希,并且不允许更改对象或分配给属性。

总体而言,如果unsafe_hash=Truefrozen=True,则该对象是可散列的。

有关更多详细信息,另请参阅原始 hashing logic table。

你没有得到什么

要获得以下特性,必须手动实现特殊方法:

拆包

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)

优化

@dataclasses.dataclass
class SlottedColor:
    __slots__ = ["r", "b", "g"]
    r : int
    g : int
    b : int

对象大小现在减小了:

>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888

在某些情况下,__slots__ 还提高了创建实例和访问属性的速度。此外,插槽不允许默认分配;否则,将引发 ValueError

在blog post 中查看更多关于插槽的信息。


汇总表

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       Feature        |       Keyword        |                      Example                       |           Implement in a Class          |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes           |  init                |  Color().r -> 0                                    |  __init__                               |
| Representation       |  repr                |  Color() -> Color(r=0, g=0, b=0)                   |  __repr__                               |
| Comparision*         |  eq                  |  Color() == Color(0, 0, 0) -> True                 |  __eq__                                 |
|                      |                      |                                                    |                                         |
| Order                |  order               |  sorted([Color(0, 50, 0), Color()]) -> ...         |  __lt__, __le__, __gt__, __ge__         |
| Hashable             |  unsafe_hash/frozen  |  Color(), Color() -> Color(r=0, g=0, b=0)    |  __hash__                               |
| Immutable            |  frozen + eq         |  Color().r = 10 -> TypeError                       |  __setattr__, __delattr__               |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  r, g, b = Color()                                 |   __iter__                              |
| Optimization+        |  -                   |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+这些方法不会自动生成,需要在数据类中手动实现。

*__ne__ 不需要,因此not implemented。


附加功能

初始化后

@dataclasses.dataclass
class RGBA:
    r : int = 0
    g : int = 0
    b : int = 0
    a : float = 1.0

    def __post_init__(self):
        self.a : int =  int(self.a * 255)


RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)

继承

@dataclasses.dataclass
class RGBA(Color):
    a : int = 0

转化次数

将数据类转换为元组或字典,recursively:

>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
'r': 128, 'g': 0, 'b': 255

限制

缺乏处理starred arguments的机制 使用nested dataclasses 可能很复杂

参考文献

R. Hettinger 在Dataclasses 上的talk:结束所有代码生成器的代码生成器 T. Hunner 的 talk 关于更简单的类:没有所有繁琐的 Python 类 Python 的 documentation 关于散列详细信息 真正的 Python 的 guide 在Python 3.7 中数据类的终极指南 A. Shaw 的blog postPython 3.7 数据类简介 E.史密斯在数据类上的github repository

【讨论】:

如果可能的话,我会给两个赞。很好的答案@pylang。先生/女士,我向您致敬;) 这是一个比公认的更好的答案。太棒了! 我很喜欢这些微博加长回复。格式良好,分为可消化的标题、代码 sn-ps 和参考部分。 知道为什么鸭子打字/类型推断,例如不支持@dataclasses.dataclass class RGB(r=255,g=0,b=0)?对于简写对我来说很重要的基本结构类型 在关闭所有功能的同时使用@dataclass 是否有意义?你最终会得到什么?【参考方案3】:

来自PEP specification:

提供了一个类装饰器,用于检查类定义 具有 PE​​P 526 中定义的类型注释的变量,“Syntax for 变量注释”。在本文档中,此类变量称为 字段。使用这些字段,装饰器添加生成的方法 对类的定义以支持实例初始化、repr、 比较方法,以及可选的其他方法,如 规范部分。这样的类称为数据类,但 这个类真的没有什么特别的:装饰者补充说 为类生成方法并返回与它相同的类 给定的。

@dataclass 生成器将方法添加到您自己定义的类中,例如 __repr____init____lt____gt__

【讨论】:

【参考方案4】:

考虑这个简单的类Foo

from dataclasses import dataclass
@dataclass
class Foo:    
    def bar():
        pass  

这是dir() 内置比较。左侧是没有@dataclass 装饰器的Foo,右侧是带有@dataclass 装饰器的Foo

这是另一个差异,在使用 inspect 模块进行比较之后。

【讨论】:

以上是关于什么是数据类,它们与普通类有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

数据封装类有什么用,比普通数据类型有什么优势?

WCF DataContract 与普通类 [重复]

请问java 单例类 与 静态类 有何不同?

计算机java抽象类与接口实验报告怎么写

java接口与抽象类有啥区别?

全局变量与类变量有何不同,它们是不是相同?