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

时间:2017-12-23 19:15:16

标签: python class python-3.7 python-dataclasses

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

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

python数据类究竟是什么以及何时最好使用它们?

5 个答案:

答案 0 :(得分:60)

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

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 7day 8,{{3}的解决方案}和day 11

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

答案 1 :(得分:14)

概述

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

  

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

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

“具有默认名称的可变命名元组”

这是后一词的意思:

  • 可变:默认情况下,可以重新分配数据类属性。您可以选择使它们不可变(请参见下面的示例)。
  • 命名元组:您拥有点缀的属性访问权限,例如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

现在可以实现排序方法(重载运算符:< > <= >=),类似于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__               |
|                      |                      |                                                    |                                         |
| 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

转化

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

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

限制


参考

  • R。 Hettinger在{em> Dataclasses上的talk:终止所有代码生成器的代码生成器
  • T。亨纳(Hunner)的talk关于更简单的类:没有所有麻烦的Python类
  • Python的documentation关于哈希详细信息
  • Real Python的guide关于 Python 3.7中的数据类最终指南
  • A。肖的blog post关于 Python 3.7数据类的简要介绍
  • E。史密斯(Smith)的github repository关于数据类

答案 2 :(得分:12)

顺便说一句。 Raymond Hettinger(Python核心开发人员)在PyCon 2018上发表了精彩的演讲:

https://www.youtube.com/watch?v=T-TwcmT6Rcw&t=1390

幻灯片在这里:https://twitter.com/raymondh/status/995693882812915712

comparison

答案 3 :(得分:2)

来自PEP specification

  

提供了一个类装饰器,用于检查类的定义   带有PEP 526中定义的类型注释的变量,&#34;语法for   变量注释&#34;。在本文档中,调用了这些变量   领域。使用这些字段,装饰器添加生成的方法   支持实例初始化的类的定义,一个repr,   比较方法,以及可选的其他方法   规格部分。这样的类称为数据类,但是   这个类真的没有什么特别之处:装饰者补充说   生成类的方法并返回它所在的类   给出。

@dataclass生成器会为您自己定义的方法添加方法,例如__repr____init____lt____gt__

答案 4 :(得分:1)

考虑这个简单的类Foo

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

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

enter image description here

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

enter image description here