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

kin*_*ian 82 python class python-3.7 python-dataclasses

使用PEP 557,数据类被引入到python标准库中.

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

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

Mar*_*ers 100

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

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))
Run Code Online (Sandbox Code Playgroud)

有了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
Run Code Online (Sandbox Code Playgroud)

同一类的装饰也可以产生比较方法(__lt__,__gt__等)和处理不变性.

namedtuple类也是数据类,但默认情况下是不可变的(以及序列).dataclasses在这方面要灵活得多,并且可以很容易地构建,以便它们可以充当与namedtuple班级相同的角色.

PEP的灵感来自该attrs项目,该项目可以做得更多(包括插槽,验证器,转换器,元数据等).

如果你想看到一些例子,我最近使用dataclasses了几个我的代码的问世解决方案,请参阅解决方案7天,8天,11天20天.

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

  • @VladimirLenin:没有类属性,只有类型注释。请参阅[PEP 526](https://www.python.org/dev/peps/pep-0526/),特别是[* Class和实例变量注释*]部分(https://www.python.org/dev / peps / pep-0526 /#class-and-instance-variable-annotations)。 (3认同)
  • @Bananach:“@dataclass”生成大致相同的“__init__”方法,并带有默认值的“quantity_on_hand”关键字参数。当您创建实例时,它将始终设置“现有数量”实例属性。因此,我的*第一个*非数据类示例使用相同的模式来回显数据类生成的代码将执行的操作。 (3认同)
  • 在第一个示例中,您是否故意隐藏具有相同名称的实例成员的类成员?请帮助理解这个习语。 (2认同)

pyl*_*ang 31

概观

这个问题已得到解决.但是,这个答案增加了一些实际的例子来帮助对数据类进行基本的理解.

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

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

"具有默认[s]的可变命名元组"

以下是后一个短语的含义:

  • mutable:默认情况下,可以重新分配dataclass属性.您可以选择使它们不可变(请参阅下面的示例).
  • namedtuple:你有点缀,属性访问就像namedtuple一个普通的类.
  • 默认值:您可以为属性指定默认值

与普通类相比,您主要是在键入样板代码.


特征

以下是数据类功能的概述(请参阅摘要表中的示例).

你得到什么

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

属性+表示+比较

import dataclasses


@dataclasses.dataclass
#@dataclasses.dataclass()                                       # alternative
class Color:
    r : int = 0
    g : int = 0
    b : int = 0
Run Code Online (Sandbox Code Playgroud)

以下默认值自动设置为True:

@dataclasses.dataclass(init=True, repr=True, eq=True)
Run Code Online (Sandbox Code Playgroud)

你可以打开什么

如果设置了适当的关键字,则可以使用其他功能True.

订购

@dataclasses.dataclass(order=True)
class Color:
    r : int = 0
    g : int = 0
    b : int = 0
Run Code Online (Sandbox Code Playgroud)

现在实现了排序方法(重载运算符< > <= >=:),类似于functools.total_ordering更强的相等性测试.

可洗,可变

@dataclasses.dataclass(unsafe_hash=True)                        # override base `__hash__`
class Color:
    ...
Run Code Online (Sandbox Code Playgroud)

尽管该对象可能是可变的(可能是不期望的),但实现了散列.

可洗,不变

@dataclasses.dataclass(frozen=True)                                 # `eq=True` (default) to be immutable 
class Color:
    ...
Run Code Online (Sandbox Code Playgroud)

现在实现了哈希,并且不允许更改对象或分配属性.

总的来说,如果是unsafe_hash=True或者对象是可以清洗的frozen=True.

另请参阅原始哈希逻辑表以及更多详细信息.

你没有得到什么

要获得以下功能,必须手动实现特殊方法:

Unpackable

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)
Run Code Online (Sandbox Code Playgroud)

优化

@dataclasses.dataclass
class SlottedColor:
    __slots__ = ["r", "b", "g"]
    r : int
    g : int
    b : int
Run Code Online (Sandbox Code Playgroud)

对象大小现在减少了:

>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888
Run Code Online (Sandbox Code Playgroud)

在某些情况下,__slots__还可以提高创建实例和访问属性的速度.此外,插槽不允许默认分配; 否则,a 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__               |
|                      |                      |                                                    |                                         |
| Unpackable+          |  -                   |  r, g, b = Color()                                 |   __iter__                              |
| Optimization+        |  -                   |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
Run Code Online (Sandbox Code Playgroud)

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

* __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)
Run Code Online (Sandbox Code Playgroud)

遗产

@dataclasses.dataclass
class RGBA(Color):
    a : int = 0
Run Code Online (Sandbox Code Playgroud)

转换

递归方式将数据类转换为元组或字典:

>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}
Run Code Online (Sandbox Code Playgroud)

限制


参考

  • R. Hettinger 关于数据讨论:代码生成器,用于结束所有代码生成器
  • T. Hunner 关于更容易类讨论:Python类没有任何关键
  • 关于散列细节的Python 文档
  • 关于Python 3.7中数据类最终指南的 Real Python 指南
  • A. Shaw的博客帖子的Python 3.7数据类的简要介绍
  • E. Smith 在数据类上的github存储库

  • 如果可以的话我会点两个赞。非常好的答案@pylang。先生/女士,我向您致敬;) (6认同)
  • 我确实喜欢这些微博长度的扩展回复。格式良好,分为易于理解的标题、代码片段和参考部分。 (4认同)
  • 这是一个比公认的答案更好的答案。太棒了! (2认同)
  • 知道为什么不支持鸭子类型/类型推断,例如`@dataclasses.dataclass class RGB(r=255,g=0,b=0)`?对于基本结构类型,速记对我来说很重要 (2认同)

Mes*_*ssa 15

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

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

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

对照


Mah*_*sam 8

来自PEP 规范

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

生成@dataclass器将方法添加到类中,否则您必须自己定义这些方法,例如、__repr__、和。__init____lt____gt__


pro*_*sti 5

考虑这个简单的类Foo

from dataclasses import dataclass
@dataclass
class Foo:    
    def bar():
        pass  
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

在此输入图像描述