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

通过PEP 557,数据类被引入python标准库。

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

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

191570 次浏览

PEP规范:

提供了类装饰器,用于检查类定义 在PEP 526中定义的带有类型注释的变量 变量Annotations"。在本文档中,这样的变量被调用 字段。使用这些字段,装饰器添加生成的方法 类的定义,以支持实例初始化,repr, 方法中描述的其他方法 规范部分。这样的类称为数据类,但是 这个类实际上没有什么特别之处:decorator补充道 生成类的方法并返回与类相同的类 鉴于. < / p >

@dataclass生成器将方法添加到类中,否则你必须像__repr____init____lt____gt__那样定义自己。

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

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在这方面要灵活得多,并且很容易被构造成填充与namedtuple类相同的角色

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

如果你想看一些例子,我最近在我的几个代码的出现解决方案中使用了dataclasses,请参阅第七天第八天天1120天的解决方案。

如果你想在Python版本<中使用dataclasses模块;3.7,然后你可以安装补丁模块(需要3.6)或使用上面提到的attrs项目。

概述

这个问题已经解决了。但是,这个答案增加了一些实际示例,以帮助对数据类的基本理解。

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

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

“可变namedtuples with default[s]”;

下面是后一个短语的意思:

  • 可变的:默认情况下,数据类属性可以被重新分配。你可以选择将它们设置为不可变的(参见下面的示例)。
  • namedtuple:像namedtuple或常规类一样有点,属性访问。
  • 默认的:你可以为属性分配默认值。

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


特性

这是数据类特性的概述(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,具有更强的相等性测试。

Hashable,可变的

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

尽管对象可能是可变的(可能不希望如此),但还是实现了哈希。

Hashable,不变的

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

现在实现了散列,不允许更改对象或为属性赋值。

总的来说,如果对象是unsafe_hash=Truefrozen=True,则对象是可哈希的。

更多细节请参见原始哈希逻辑表

你得不到什么

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

拆包

@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

有关博客中的槽的更多信息。


汇总表

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__不需要,因此未实现


附加功能

Post-initialization

@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

转换

将数据类转换为元组或字典,递归地:

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

限制


参考文献

  • R. Hettinger的说话数据类:结束所有代码生成器的代码生成器
  • T. Hunner的说话更简单的类:没有任何繁琐的Python类
  • Python关于哈希细节的文档
  • 真正的Python在Python 3.7中数据类的终极指南上的指南
  • A. Shaw的博客Python 3.7数据类的简要介绍
  • E. Smith的github库dataclasses

考虑这个简单的类Foo

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

下面是dir()内置比较。左边是没有@dataclass装饰器的Foo,右边是有@dataclass装饰器的Foo

enter image description here

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

enter image description here