Python 在版本 3.7 (PEP 557) 中引入了dataclass。dataclass允许你用更少的代码和更多的开箱即用功能来定义类。
下面定义了一个具有两个实例属性 name 和 age 的常规 Person 类:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
这个 Person 类具有初始化 name 和 age 属性的__init__ 方法。
如果你想要一个 Person 对象的字符串表示,你需要实现__str__ 或 __repr__方法。另外,如果要通过属性比较 Person 类的两个实例,则需要实现__eq__方法。
但是,如果你使用数据类,你将拥有所有这些功能(甚至更多),而无需实现这些 dunder 方法。
要使 Person 类成为数据类,请执行以下步骤:
首先,从 dataclasses 模块导入 dataclass 装饰器:
from dataclasses import dataclass
其次,用 dataclass 装饰器装饰 Person 类并声明属性:
@dataclass
class Person:
name: str
age: int
在这个例子中,Person 类有两个属性 name 类型为 str 和 age 类型为 int, 这样@dataclass 装饰器隐式创建__init__方法,如下所示:
def __init__(name: str, age: int)
请注意,类中声明的属性的顺序将决定__init__ 方法中参数的顺序。
你可以创建 Person 的对象:
p1 = Person('John', 25)
当打印出 Person 的对象时,你会得到一个可读的格式:
print(p1)
输出:
Person(name='John', age=25)
此外,如果你比较两个具有相同属性值的 Person 对象,它将返回 True。例如:
p1 = Person('John', 25)
p2 = Person('John', 25)
print(p1 == p2)
输出
True
下面讨论数据类提供的其他功能。
默认值
使用常规类时,你可以定义属性的默认值。例如,以下 Person 类的 iq 参数的默认值为 100。
class Person:
def __init__(self, name, age, iq=100):
self.name = name
self.age = age
self.iq = iq
要为数据类中的属性定义默认值,请将其分配给属性,如下所示:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
iq: int = 100
print(Person('John Doe', 25))
与参数规则一样,具有默认值的属性必须出现在没有默认值的属性之后。因此,以下代码将不起作用:
from dataclasses import dataclass
@dataclass
class Person:
iq: int = 100
name: str
age: int
转换为元组或字典
dataclasses 模块具有 astuple() 和 asdict() 函数,它们将数据类的实例转换为元组和字典。例如:
from dataclasses import dataclass, astuple, asdict
@dataclass
class Person:
name: str
age: int
iq: int = 100
p = Person('John Doe', 25)
print(astuple(p))
print(asdict(p))
输出:
('John Doe', 25, 100)
{'name': 'John Doe', 'age': 25, 'iq': 100}
创建不可变对象
要从数据类创建只读对象,可以将数据类装饰器的冻结参数设置为 True。例如:
from dataclasses import dataclass, astuple, asdict
@dataclass(frozen=True)
class Person:
name: str
age: int
iq: int = 100
如果你在创建对象后尝试更改其属性,则会收到错误消息。例如:
p = Person('Jane Doe', 25)
p.iq = 120
错误信息:
dataclasses.FrozenInstanceError: cannot assign to field 'iq'
自定义属性行为
如果不想在 __init__ 方法中初始化属性,可以使用 dataclasses 模块中的 field() 函数。
以下示例定义了使用 __init__方法初始化的 can_vote 属性:
from dataclasses import dataclass, field
class Person:
name: str
age: int
iq: int = 100
can_vote: bool = field(init=False)
field() 函数有多个有趣的参数,例如 repr、hash、compare 和 metadata。
如果要初始化一个依赖于另一个属性值的属性,可以使用__post_init__ 方法。顾名思义,Python 在 __init__方法之后调用 __post_init__ 方法。
下面使用__post_init__ 方法根据 age 属性初始化 can_vote 属性:
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int
iq: int = 100
can_vote: bool = field(init=False)
def __post_init__(self):
print('called __post_init__ method')
self.can_vote = 18 <= self.age <= 70
p = Person('Jane Doe', 25)
print(p)
输出:
called the __post_init__ method
Person(name='Jane Doe', age=25, iq=100, can_vote=True)
对对象进行排序
默认情况下,数据类实现 __eq__方法。
要允许不同类型的比较,如__lt__、__lte__、__gt__、__gte__,你可以将 @dataclass 装饰器的 order 参数设置为 True:
@dataclass(order=True)
通过这样做,数据类将按每个字段对对象进行排序,直到找到不相等的值。
在实践中,你经常希望通过特定属性而不是所有属性来比较对象。为此,你需要定义一个名为 sort_index 的字段并将其值设置为要排序的属性。
例如,假设你有一个 Person 对象列表,并希望按年龄对它们进行排序:
members = [
Person('John', 25),
Person('Bob', 35),
Person('Alice', 30)
]
因为,需要:
- 首先,将 order=True 参数传递给 @dataclass 装饰器。
- 其次,定义 sort_index 属性并将其 init 参数设置为 False。
- 第三,在 __post_init__方法中将 sort_index 设置为 age 属性,以按年龄对 Person 的对象进行排序。
from dataclasses import dataclass, field
@dataclass(order=True)
class Person:
sort_index: int = field(init=False, repr=False)
name: str
age: int
iq: int = 100
can_vote: bool = field(init=False)
def __post_init__(self):
self.can_vote = 18 <= self.age <= 70
# sort by age
self.sort_index = self.age
members = [
Person(name='John', age=25),
Person(name='Bob', age=35),
Person(name='Alice', age=30)
]
sorted_members = sorted(members)
for member in sorted_members:
print(f'{member.name}(age={member.age})')
输出:
John(age=25)
Alice(age=30)
Bob(age=35)
总结
- 使用 dataclasses 模块中的 @dataclass 装饰器使类成为数据类。数据类对象默认实现__eq__和__str__。
- 使用 astuple() 和 asdict() 函数将数据类的对象转换为元组和字典。
- 使用 freeze=True 定义一个对象不可变的类。
- 使用 __post_init__ 方法初始化依赖于其他属性的属性。
- 使用 sort_index 指定数据类对象的排序属性。