问题:什么是数据类,它们与普通类有何不同?
使用PEP 557,数据类被引入到python标准库中。
它们使用@dataclass
装饰器,并且应该是“默认情况下的可变命名元组”,但是我不确定我是否真正理解这是什么意思,以及它们与普通类的区别。
python数据类到底是什么,什么时候最好使用它们?
回答 0
数据类只是用于存储状态的常规类,不仅仅包含许多逻辑。每次创建一个主要由属性组成的类时,就创建了一个数据类。
该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
在这方面更灵活,并且可以轻松地进行结构化,使其可以充当namedtuple
类的相同角色。
PEP受该attrs
项目的启发,该项目可以做更多的事情(包括广告位,验证器,转换器,元数据等)。
如果你想看到一些例子,我最近使用dataclasses
了几个我的代码的问世解决方案,请参阅解决方案7天,8天,11天和20天。
如果要dataclasses
在<3.7以下的Python版本中使用模块,则可以安装向后移植的模块(需要3.6)或使用上述attrs
项目。
回答 1
总览
这个问题已经解决。但是,此答案添加了一些实际示例以帮助对数据类进行基本了解。
python数据类到底是什么,什么时候最好使用它们?
- 代码生成器:生成样板代码;您可以选择在常规类中实现特殊方法,也可以让数据类自动实现它们。
- 数据容器:保存数据的结构(例如,元组和字典),通常具有点分,属性访问权限,例如类
namedtuple
等。
“具有默认值的可变命名元组”
这是后一词的意思:
- mutable:默认情况下,可以重新分配数据类属性。您可以选择使它们不可变(请参见下面的示例)。
- namedtuple:您具有点分,属性访问权限,例如
namedtuple
或常规类。 - default:您可以为属性分配默认值。
与普通类相比,您主要节省键入样板代码的费用。
特征
这是数据类功能的概述(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
。
另请参阅原始哈希逻辑表。
你没有得到什么
要获得以下功能,必须手动实施特殊方法:
开箱
@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
引发a。
汇总表
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| 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__
不需要,因此也没有实现。
附加功能
后初始化
@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
转换次数
递归将数据类转换为元组或字典:
>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}
局限性
参考资料
回答 2
根据PEP规范:
提供了一个类装饰器,该类装饰器检查类定义中具有类型注释的变量,如PEP 526“变量注释的语法”中所定义。在本文档中,此类变量称为字段。装饰器使用这些字段将生成的方法定义添加到类中,以支持实例初始化,repr,比较方法以及(可选)规范部分中描述的其他方法。这样的类称为数据类,但该类实际上没有什么特别的:装饰器将生成的方法添加到该类中,并返回与该类相同的类。
该@dataclass
生成器增加方法的类,否则你自己定义一样__repr__
,__init__
,__lt__
,和__gt__
。
回答 3
考虑这个简单的类 Foo
from dataclasses import dataclass
@dataclass
class Foo:
def bar():
pass
这是dir()
内置的比较。左侧是Foo
没有@dataclass装饰器的,右侧是带有@dataclass装饰器的。
在使用inspect
模块进行比较之后,这是另一个差异。