将PEP 557数据类引入python标准库。
他们使用@dataclass
装饰器,它们应该是“默认的可变的命名元组”,但我不确定我是否理解这实际意味着什么以及它们与普通类的区别。
python数据类究竟是什么以及何时最好使用它们?
答案 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 7,day 8,{{3}的解决方案}和day 11。
如果要在Python版本中使用dataclasses
模块< 3.7,然后你可以安装day 20(要求3.6)或使用上面提到的attrs
项目。
答案 1 :(得分:14)
问题已解决。但是,此答案添加了一些实际示例来帮助对数据类进行基本了解。
什么是python数据类,什么时候最好使用它们?
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=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__ |
| | | | |
| 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}
限制
答案 2 :(得分:12)
顺便说一句。 Raymond Hettinger(Python核心开发人员)在PyCon 2018上发表了精彩的演讲:
https://www.youtube.com/watch?v=T-TwcmT6Rcw&t=1390
幻灯片在这里:https://twitter.com/raymondh/status/995693882812915712
答案 3 :(得分:2)
提供了一个类装饰器,用于检查类的定义 带有PEP 526中定义的类型注释的变量,&#34;语法for 变量注释&#34;。在本文档中,调用了这些变量 领域。使用这些字段,装饰器添加生成的方法 支持实例初始化的类的定义,一个repr, 比较方法,以及可选的其他方法 规格部分。这样的类称为数据类,但是 这个类真的没有什么特别之处:装饰者补充说 生成类的方法并返回它所在的类 给出。
@dataclass
生成器会为您自己定义的方法添加方法,例如__repr__
,__init__
,__lt__
和__gt__
。
答案 4 :(得分:1)