定义
dataclass 是python 3.7新增的一个模块,适用于存储数据对象
数据对象特性:
1、存储数据并代表某种数据类型
2、可以与同一类型的其他对象比较
用法
提供了一个装饰器dataclass,用于将类转换为dataclass
语法:
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
参数:
init: 如果为真值(默认),将生成一个 __ init__() 方法。如果类已定义 __ init__() ,则忽略此参数。
repr :如果为真值(默认),将生成一个 __repr__() 方法。 生成的 repr 字符串将具有类名以及每个字段的名称和 repr ,按照它们在类中定义的顺序。不包括标记为从 repr 中排除的字段。 例如:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)。如果类已定义 __repr__() ,则忽略此参数。
eq :如果为true(默认值),将生成 __eq__() 方法。此方法将类作为其字段的元组按顺序比较。比较中的两个实例必须是相同的类型。如果类已定义 __eq__() ,则忽略此参数。
order :如果为真值(默认为 False ),则 __lt__() 、 __ le__() 、 __gt__() 和 __ge__() 方法将生成。 这将类作为其字段的元组按顺序比较。比较中的两个实例必须是相同的类型。如果 order 为真值并且 eq 为假值 ,则引发 ValueError 。如果类已经定义了 __lt__() 、 __le__() 、 __gt__() 或者 __ge__() 中的任意一个,将引发 TypeError 。
unsafe_hash :如果为 False (默认值),则根据 eq 和 frozen 的设置方式生成 __hash__() 方法。__hash__()
由内置的 hash()
使用,当对象被添加到散列集合(如字典和集合)时。有一个 __hash__()
意味着类的实例是不可变的。可变性是一个复杂的属性,取决于程序员的意图, __eq__()
的存在性和行为,以及 dataclass()
装饰器中 eq
和 frozen
标志的值。
frozen: 如为真值 (默认值为 False),则对字段赋值将会产生异常。 这模拟了只读的冻结实例。 如果在类中定义了 __setattr__() 或 __delattr__() 则将会引发 TypeError。
新特性
下边是dataclass带来的变化,分为初始化、表示、数据比较、可调用的装饰、Frozen(不可变)实例、后期初始化处理、继承
初始化
from dataclasses import dataclass
# 初始化
# 未使用dataclass
class Number:
def __init__(self, val):
self.val = val
one = Number(1)
print(one.val) # 1
# 使用dataclass
# 可以设置默认值val:int = 0
@dataclass
class Number:
val:int
one = Number(1)
print(one.val) # 1
说明:
dataclass带来的变化:
1、无需定义__init__,然后将值赋给self
2、以更加易读的方式定义了成员属性及类型提示
表示
# 表示
# 未使用dataclass
class Number:
def __init__(self, val):
self.val = val
one = Number(1)
print(one) # <__main__.Number object at 0x000000000216C2B0>
# 可在类中定义一个__repr__方法来表示
class Number:
def __init__(self, val):
self.val = val
def __repr__(self):
return str(self.val)
one = Number(1)
print(one) # 1
# 使用dataclass
@dataclass
class Number:
val:int = 0
one = Number(1)
print(one) # Number(val=1)
说明:
dataclass 会自动添加一个 __repr__ 函数,这样我们就不必手动实现它
数据比较
# 数据比较
# 未使用dataclass
class Number:
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
def __lt__(self, other):
return self.val < other.val
# 使用dataclass
@dataclass(order = True)
class Number:
val:int = 0
import random
a = [Number(random.randint(1,10)) for _ in range(10)]
print(a) # [Number(val=3), Number(val=5), Number(val=5), Number(val=4), Number(val=1), Number(val=5), Number(val=10), Number(val=8), Number(val=1), Number(val=5)]
sorted_a = sorted(a)
print(sorted_a) # [Number(val=1), Number(val=1), Number(val=3), Number(val=4), Number(val=5), Number(val=5), Number(val=5), Number(val=5), Number(val=8), Number(val=10)]
reverse_sorted_a = sorted(a, reverse= True)
print(reverse_sorted_a) # [Number(val=10), Number(val=8), Number(val=5), Number(val=5), Number(val=5), Number(val=5), Number(val=4), Number(val=3), Number(val=1), Number(val=1)]
说明:
- 不需要定义 __eq__ 和 __lt__ 方法,因为当 order = True 被调用时,dataclass 装饰器会自动将它们添加到我们的类定义
- 自动生成的方法属性顺序与你在dataclass类中定义的顺序一致
- 内置的sorted函数依赖于比较两个对象
可调用的装饰
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
…
说明:
1.参数
init:默认将生成 __init__ 方法。如果传入 False,那么该类将不会有 __init__ 方法。
repr:__repr__ 方法默认生成。如果传入 False,那么该类将不会有 __repr__ 方法。
eq:默认将生成 __eq__ 方法。如果传入 False,那么 __eq__ 方法将不会被 dataclass 添加,但默认为 object.__eq__。
order:默认将生成 __gt__、__ge__、__lt__、__le__ 方法。如果传入 False,则省略它们。
2.可以有选择性地定义所有dunder(双下划线方法Double UNDERscore,即魔法方法)
修改默认生成的函数:@dataclass(repr = False)
Frozen(不可变)实例
# 不可变实例
# 使用dataclass
@dataclass(frozen=True)
class Number:
val:int = 0
one = Number(1)
print(one.val) # 1
# one.val = 2 # 报错'Number' object attribute 'val' is read-only
说明:
frozen实例不可变的特性可以用来存储常数、设置
后期初始化处理
# 后期初始化处理
# 未使用 dataclass
import math
class Float:
def __init__(self, val = 0):
self.val = val
self.process()
def process(self):
self.decimal, self.integer = math.modf(self.val)
a = Float(2.2)
print(a.decimal) # 0.20000000000000018
print(a.integer) # 2.0
# 使用 dataclass
import math
@dataclass
class FloatNumber:
val:float = 0.0
def __post_init__(self):
self.decimal, self.integer = math.modf(self.val)
a = FloatNumber(2.2)
print(a.decimal) # 0.20000000000000018
print(a.integer) # 2.0
说明:
- __init__方法初始化失去了在变量被赋值之后立即需要的函数调用或处理的灵活性。post_init可以弥补这种场景的缺陷,处理后期初始化操作
- 自动生成的 __init__() 会被 __post_init__() hook(拦截),即:自动生成的 __init__() 会调用 __post_init__()。
继承
# 继承
# 父类中定义的属性将在子类中可用
@dataclass
class Person:
age: int = 0
name: str = '1'
@dataclass
class Student(Person):
grade: int = 0
s = Student(18, "mingliang", 100)
print(s.age)
print(s.name)
print(s.grade)
# 继承过程中__post_init__的行为
@dataclass
class A:
a: int
def __post_init__(self):
print('A')
@dataclass
class B(A):
b: int
def __post_init__(self):
print('B')
a = B(1, 2) # B 只有 B的 __post_init__ 被调用
@dataclass
class B(A):
b: int
def __post_init__(self):
super().__post_init__() # 父类的函数可以使用 super 调用
print('B')
a = B(1, 2) # A B
说明:
- 父类中定义的属性将在子类中可用
- 父类的函数可以使用 super 调用
参考链接:https://docs.python.org/zh-cn/3/library/dataclasses.html