使用 PEP 557 将数据类引入 python 标准库。
它们使用 @dataclass
装饰器,它们应该是“具有默认值的可变命名元组”,但我不确定我理解这实际上意味着什么以及它们与普通类有何不同。
python数据类到底是什么,什么时候最好使用它们?
namedtuple
是不可变的,不能具有属性的默认值,而数据类是可变的并且可以具有它们。
@dataclass
上的一个问题不会导致网站崩溃。
namedtuples
可以有默认值。看看这里:stackoverflow.com/questions/11351032/…
数据类只是用于存储状态的常规类,而不是包含大量逻辑。每次你创建一个主要由属性组成的类时,你就创建了一个数据类。
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,它可以做更多的事情(包括插槽、验证器、转换器、元数据等)。
如果您想查看一些示例,我最近将 dataclasses
用于我的几个 Advent of Code 解决方案,请参阅 day 7、day 8、day 11 和 day 20 的解决方案。
如果你想在 Python 版本中使用 dataclasses
模块 < 3.7,那么您可以安装 backported module(需要 3.6)或使用上面提到的 attrs
项目。
概述
该问题已得到解决。但是,此答案添加了一些实际示例,以帮助基本了解数据类。
python数据类到底是什么,什么时候最好使用它们?
代码生成器:生成样板代码;您可以选择在常规类中实现特殊方法或让数据类自动实现它们。数据容器:保存数据的结构(例如元组和字典),通常带有点,属性访问,例如类、namedtuple 等。
“具有默认值 [s] 的可变命名元组”
以下是后一句话的意思:
可变的:默认情况下,可以重新分配数据类属性。您可以选择使它们不可变(参见下面的示例)。
namedtuple:你有像 namedtuple 或普通类一样的点属性访问。
默认值:您可以为属性分配默认值。
与普通类相比,您主要节省键入样板代码。
特征
这是数据类功能的概述(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
,则该对象是可散列的。
另请参阅原始 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__ |
| | | | |
| Unpacking+ | - | 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 关于数据类的演讲:结束所有代码生成器的代码生成器
T. Hunner 关于更简单的类的演讲:没有所有繁琐的 Python 类
Python 关于散列细节的文档
Real Python 的 Python 3.7 数据类终极指南指南
A. Shaw 关于 Python 3.7 数据类的简要介绍的博文
E. Smith 关于数据类的 github 存储库
@dataclasses.dataclass class RGB(r=255,g=0,b=0)
吗?对于速记对我很重要的基本结构类型
@dataclass
是否有意义?你最终会得到什么?
提供了一个类装饰器,它检查具有类型注释的变量的类定义,如 PEP 526,“变量注释的语法”中定义的。在本文档中,此类变量称为字段。使用这些字段,装饰器将生成的方法定义添加到类中,以支持实例初始化、repr、比较方法和可选的其他方法,如规范部分所述。这样的类称为数据类,但该类实际上并没有什么特别之处:装饰器将生成的方法添加到类并返回给定的相同类。
@dataclass
生成器向您自己定义的类添加方法,例如 __repr__
、__init__
、__lt__
和 __gt__
。
考虑这个简单的类 Foo
from dataclasses import dataclass
@dataclass
class Foo:
def bar():
pass
这是 dir()
内置比较。左侧是没有 @dataclass 装饰器的 Foo
,右侧是带有 @dataclass 装饰器的 Foo
。
https://i.stack.imgur.com/pvmn9.png
在使用 inspect
模块进行比较之后,这是另一个差异。
https://i.stack.imgur.com/ARkeJ.png
@dataclass
生成大致相同的__init__
方法,其中quantity_on_hand
关键字参数具有默认值。创建实例时,它会始终设置quantity_on_hand
实例属性。因此,我的 first 非数据类示例使用相同的模式来回显数据类生成的代码将执行的操作。__post_init__
方法中进行规范化。