2024彻底搞懂Python的Dataclass(3)
本文较长,会分为几期,目标是给大家讲解
Python的dataclass该怎么用,以及它是如何工作的,而不只是和大家一起创建一个dataclass
本文基于Python的版本是3.11,不同的版本的python,dataclass可能会有不同的差异,这是因为dataclass是一个正在不断演进的python特性,变化较大。
本文为连载,如果没有看过前面的内容,建议先跳转下面的链接进行“补课”
比较和排序
想要dataclass支持排序是非常简单的,只需要加上order=True
from dataclasses import dataclass@dataclass(frozen=True, order=True)
class Position:
x: float = 0
y: float = 0
>>> p1 = Position(1, 2)
>>> p2 = Position(1, 3)
>>> p1 == p2, p1 > p2, p1 < p2, p1>=p2, p1<=p2
(False, False, True, False, True)
>>> sorted([p1, p2])
[Position(x=1, y=2), Position(x=1, y=3)]
dataclass默认的比较是根据tuple (x, y)的大小比较,实现简单(dataclass的比较函数我们是可以重置的,也就是根据具体需求去重新自己实现,但是这里我们就不演示了),但是即使如此,传统class要实现这个功能还是需要很多额外代码的,这里我们直接把实现贴出来(这里用了total_ordering简化了步骤,否则我们要实现的内置函数会更多,大于,大于等于,小于,小于等于)。
from functools import total_ordering@total_ordering
class Position:
def __init__(self, x: float = 0, y: float = 0,):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __str__(self):
return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
def __repr__(self):
return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
def __eq__(self, other):
if self.__class__ == other.__class__:
return (self.x, self.y) == (other.x, other.y)
return NotImplemented
def __hash__(self):
return hash((self.x, self.y))
def __lt__(self, other):
if self.__class__ == other.__class__:
return (self.x, self.y) < (other.x, other.y)
return NotImplemented
测试
>>> p1 = Position(1, 2)
>>> p2 = Position(1, 3)
>>> p1 == p2, p1 > p2, p1 < p2, p1>=p2, p1<=p2
(False, False, True, False, True)
>>> sorted([p1, p2])
[Position(x=1, y=2), Position(x=1, y=3)]
>>>
序列化成字典或者元组
dataclass的实例可以非常方便的序列化成字典或者元组。
from dataclasses import dataclass, asdict, astuple@dataclass(frozen=True, order=True)
class Position:
x: float = 0
y: float = 0
转化成字典或者元组
>>> p = Position(1, 2)
>>> asdict(p)
{'x': 1, 'y': 2}
>>> astuple(p)
(1, 2)
>>>
而传统的class要实现这个功能则需要我们自己写代码,比如
from functools import total_ordering@total_ordering
class Position:
def __init__(self, x: float = 0, y: float = 0,):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __str__(self):
return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
def __repr__(self):
return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
def __eq__(self, other):
if self.__class__ == other.__class__:
return (self.x, self.y) == (other.x, other.y)
return NotImplemented
def __hash__(self):
return hash((self.x, self.y))
def __lt__(self, other):
if self.__class__ == other.__class__:
return (self.x, self.y) < (other.x, other.y)
return NotImplemented
def asdict(self):
return {
'x': self.x,
'y': self.y
}
def astuple(self):
return (self.x, self.y)
测试
>>> p = Position(1, 2)
>>> p
Position(x=1, y=2)
>>> p.asdict()
{'x': 1, 'y': 2}
>>> p.astuple()
(1, 2)
只允许关键字参数
在dataclass里通过添加参数kw_only=True,可以限制实例初始化的时候只允许关键字参数(keyword only argument),而不能使用位置参数(positional argument).
from dataclasses import dataclass, asdict, astuple@dataclass(frozen=True, order=True,kw_only=True)
class Position:
x: float = 0
y: float = 0
测试
>>> p = Position(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Position.__init__() takes 1 positional argument but 3 were given
>>> p = Position(x=1, y=2)
>>> p
Position(x=1, y=2)
>>>
当然,也可以让部分参数是keyword only argument (比如,只让y是keyword only argument)
from dataclasses import dataclass, KW_ONLY@dataclass(frozen=True, order=True)
class Position:
x: float = 0
_: KW_ONLY
y: float = 0
测试
>>> p = Position(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Position.__init__() takes from 1 to 2 positional arguments but 3 were given
>>> p = Position(1, y=2)
>>> p
Position(x=1, y=2)
>>>
~~~~~~✍️未完待续✍️~~~~~~