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

What are data classes and how are they different from common classes?

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

他们使用了 @dataclass 装饰器,他们应该是 "mutable namedtuples with default" 但我不太确定我理解这到底意味着什么以及它们与普通 [=23] 有何不同=].

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

来自PEP specification

A class decorator is provided which inspects a class definition for variables with type annotations as defined in PEP 526, "Syntax for Variable Annotations". In this document, such variables are called fields. Using these fields, the decorator adds generated method definitions to the class to support instance initialization, a repr, comparison methods, and optionally other methods as described in the Specification section. Such a class is called a Data Class, but there's really nothing special about the class: the decorator adds generated methods to the class and returns the same class it was given.

@dataclass 生成器将方法添加到 class,否则您将自己定义为 __repr____init____lt____gt__.

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

dataclasses 模块的作用是使 更容易 创建数据 classes。它为您处理了很多样板文件。

当您的数据 class 必须可哈希时,这尤其有用;因为这需要 __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

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

namedtuple classes 也是数据 classes,但默认情况下是不可变的(也是序列)。 dataclasses 在这方面要灵活得多,并且可以很容易地构造成 .

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

如果你想看一些例子,我最近使用了 dataclasses 用于我的几个 Advent of Code solutions, see the solutions for day 7, day 8, day 11 and day 20

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

概览

问题已解决。但是,此答案添加了一些实际示例以帮助基本理解 dataclasses.

What exactly are python data classes and when is it best to use them?

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

"mutable namedtuples with default[s]"

后一个短语的含义如下:

  • mutable:默认情况下,dataclass属性可以重新赋值。您可以选择使它们不可变(参见下面的示例)。
  • namedtuple:您点缀了属性访问,如 namedtuple 或常规 class.
  • default: 您可以为属性指定默认值。

与常见的 classes 相比,您主要节省了键入样板代码的时间。


特点

这是数据class 特征的概述(TL;DR?请参阅下一节中的摘要 Table)。

你得到什么

以下是您默认从数据classes.

获得的特征

属性+表示+比较

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 中查看有关广告位的更多信息。


总结Table

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

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

不需要

* __ne__ 因此 not implemented.


附加功能

Post-初始化

@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

转化次数

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

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

限制

  • 缺乏处理机制
  • 使用 可能会很复杂

参考资料

  • R。 Hettinger 的 talk on Dataclasses: 结束所有代码生成器的代码生成器
  • T。 Hunner 的 talk 更简单 类:Python 类 没有所有 Cruft
  • Python 的 documentation 关于散列的详细信息
  • 真正的Python的guide数据终极指南类中Python3.7
  • 一个。 Shaw 的 blog post 关于 Python 3.7 数据的简要介绍 classes
  • E. dataclasses
  • 上史密斯的 github repository

考虑这个简单的问题 class Foo

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

这里是dir()built-in比较。 left-hand 一侧是没有 @dataclass 装饰器的 Foo,右边是带有 @dataclass 装饰器的

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