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

2024-06-16 08:27:19 发布

您现在位置:Python中文网/ 问答频道 /正文

WithPEP 557数据类被引入到python标准库中。

它们使用@dataclassdecorator,它们应该是“带默认值的可变namedtuples”,但我不确定这到底意味着什么,以及它们与普通类的区别。

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


Tags: 数据标准区别namedtupleswithpepdataclassdecorator
3条回答

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生成器将方法添加到类中,否则您会将这些方法定义为__repr____init____lt____gt__

概述

这个问题已经解决了。然而,这个答案添加了一些实际的例子来帮助对数据类的基本理解。

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

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

"mutable namedtuples with default[s]"

后一句话的意思是:

  • 可变:默认情况下,可以重新分配数据类属性。您可以选择使它们不可变(参见下面的示例)。
  • namedtuple:您有点式的属性访问,如namedtuple或常规类。
  • 默认值:可以为属性指定默认值

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


特点

下面是数据类特性的概述(请参阅摘要表中的示例)。

你得到的

以下是默认情况下从数据类获得的功能。

属性+表示+比较

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

现在实现了排序方法(重载运算符:< > <= >=),类似于使用更强的等式测试的^{}

可哈希,可变

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

转换

将数据类转换为元组或dict,recursively

>>> 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的{a9}on简单类:没有所有Cruft的Python类
  • Python关于散列细节的documentation
  • 真正的Python的guideonPython 3.7中数据类的最终指南
  • A、 Shaw的blog postonPython 3.7数据类简介
  • E、 史密斯关于数据类的github repository

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

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

同一个类decorator还可以生成比较方法(__lt____gt__等)并处理不变性。

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

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

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

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

相关问题 更多 >