什么是数据类?它与普通类有何不同?
- 2025-03-05 09:16:00
- admin 原创
- 83
问题描述:
PEP 557将数据类引入 Python 标准库。它表示,通过应用@dataclass
下面显示的装饰器,它将生成“除其他外,一个__init__()
”。
from dataclasses import dataclass @dataclass 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
它还说数据类是“具有默认值的可变命名元组”,但我不明白这是什么意思,也不明白数据类与普通类有何不同。
什么是数据类以及何时最适合使用它们?
解决方案 1:
数据类只是用于存储状态的常规类,而不是包含大量逻辑。每次创建一个主要由属性组成的类时,您都会创建一个数据类。
该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
(基于PEP 示例的示例)。
相同的类装饰器还可以生成比较方法(__lt__
、__gt__
等)并处理不变性。
namedtuple
类也是数据类,但默认情况下是不可变的(也是序列)。dataclasses
在这方面更加灵活,并且可以轻松地构造,以便它们可以充当与类相同的角色namedtuple
。
该 PEP 受到attrs
项目的启发,它可以做更多的事情(包括插槽、验证器、转换器、元数据等)。
如果您想看一些示例,我最近使用了dataclasses
几个Advent of Code解决方案,请参阅第 7 天、第 8 天、第 11 天和第 20 天的解决方案。
dataclasses
如果您想在 Python 版本 <3.7 中使用模块,那么您可以安装反向移植的模块(需要 3.6)或使用attrs
上面提到的项目。
解决方案 2:
概述
问题已经得到解决。但是,这个答案添加了一些实际示例,以帮助对数据类进行基本的理解。
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(slots=True) # py310+
class SlottedColor:
#__slots__ = ["r", "b", "g"] # alternative
r : int
g : int
b : int
物体尺寸现已减小:
>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888
slots=True
已添加到 Python 3.10 中。(感谢@ajskateboarder)。
在某些情况下,slots=True
/__slots__
还可以提高创建实例和访问属性的速度。此外,插槽不允许默认分配;否则,ValueError
会引发 a。如果__slot__
已经存在,slots=True
将导致 a TypeError
。
请参阅此博客文章中有关插槽的更多信息。
查看有关Python 3.10+ 中添加的更多参数:match_args
,,,。kw_only
`slots`weakref_slot
你得不到什么
为了获得以下功能,必须手动实现特殊方法:
拆开
@dataclasses.dataclass
class Color:
r : int = 0
g : int = 0
b : int = 0
def __iter__(self):
yield from dataclasses.astuple(self)
摘要表
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| 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__ |
| Optimization | slots | sys.getsizeof(SlottedColor) -> 888 | __slots__ |
| | | | |
| Unpacking+ | - | r, g, b = Color() | __iter__ |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
__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}
限制
缺乏处理带星号参数的机制
使用嵌套数据类可能很复杂
参考
R. Hettinger关于Dataclasses 的演讲:终结所有代码生成器的代码生成器
T. Hunner 的演讲:更简单的类:没有繁琐内容的 Python 类
Python关于哈希细节的文档
Real Python 的Python 3.7 数据类终极指南
A. Shaw 的博客文章《Python 3.7 数据类简介》
E. Smith 的github上关于dataclasses 的仓库
解决方案 3:
来自PEP 规范:
提供了一个类装饰器,用于检查类定义中是否有带类型注释的变量,如 PEP 526“变量注释语法”中定义的那样。在本文档中,此类变量称为字段。装饰器使用这些字段将生成的方法定义添加到类中,以支持实例初始化、repr、比较方法以及“规范”部分中描述的可选其他方法。这样的类称为数据类,但该类实际上并没有什么特别之处:装饰器将生成的方法添加到类中并返回给定的相同类。
生成@dataclass
器向类中添加方法,否则您必须自己定义这些方法__repr__
,例如__init__
、、__lt__
和__gt__
。
解决方案 4:
考虑这个简单的类Foo
from dataclasses import dataclass
@dataclass
class Foo:
def bar():
pass
这是dir()
内置的比较。左侧是Foo
没有 @dataclass 装饰器的,右侧是有 @dataclass 装饰器的。
inspect
这是使用模块进行比较后的另一个差异。
解决方案 5:
@dataclass 可以轻松创建用于存储数据的类,而无需编写额外的代码。它会自动创建init () 和其他方法,从而使您的代码更简洁、更易读。例如:如果您创建一个类来存储数据,则必须编写:
class Person:
def __init__(self, name, age):
self.n
name = name
self.age = age
def __repr__(self): # So we can print the object nicely
return f"Person(name='{self.name}', age={self.age})"
p1 = Person("Alice", 25)
print(p1) # Output: Person(name='Alice', age=25)
对于简单的任务来说代码太多了,但是使用@dataclass我们可以使它变得更简单:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
p1 = Person("Alice", 25)
print(p1) # Output: Person(name='Alice', age=25)
扫码咨询,免费领取项目管理大礼包!