314

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

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

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

4

4 回答 4

349

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

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在这方面更加灵活,并且可以很容易地进行结构化,以便它们可以扮演与class相同的角色namedtuple

PEP 受到该attrs项目的启发,该项目可以做更多的事情(包括插槽、验证器、转换器、元数据等)。

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

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

于 2017-12-23T19:22:32.857 回答
187

概述

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

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

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

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

以下是后一句话的意思:

  • mutable:默认情况下,可以重新分配数据类属性。您可以选择使它们不可变(参见下面的示例)。
  • 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=True总体而言,如果是或,则该对象是可散列的frozen=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__还提高了创建实例和访问属性的速度。此外,插槽不允许默认分配;否则, aValueError被提高。

在此博客文章中查看有关插槽的更多信息。


汇总表

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__不是必需的,因此未实施


附加的功能

初始化后

@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关于散列细节的文档
  • Real Python 的Python 3.7 数据类终极指南指南
  • A. Shaw关于Python 3.7 数据类的简要介绍的博文
  • E. Smith 关于数据类的github存储
于 2018-09-11T19:38:18.750 回答
4

PEP 规范

提供了一个类装饰器,它检查具有类型注释的变量的类定义,如 PEP 526,“变量注释的语法”中定义的。在本文档中,此类变量称为字段。使用这些字段,装饰器将生成的方法定义添加到类中,以支持实例初始化、repr、比较方法和可选的其他方法,如规范部分所述。这样的类称为数据类,但该类实际上并没有什么特别之处:装饰器将生成的方法添加到类并返回给定的相同类。

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

于 2017-12-23T19:21:28.647 回答
3

考虑这个简单的类Foo

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

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

在此处输入图像描述

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

在此处输入图像描述

于 2019-05-23T19:12:05.680 回答