一文带你看懂python dataclass装饰器
Python 3.7引入了dataclass装饰器,dataclass装饰器可以声明Python类为数据类。数据类适合用来存储数据,一般而言它具有如下特征:
- 数据类表示某种数据类型,数据对象代表一种特定类的实体,包含了实体的属性。
- 同类型的对象之间可以进行比较。例如,大于、小于或等于。
添加@dataclass就可以声明一个类为数据类:
from dataclasses import dataclass
@dataclass
class A:
…
就其本质而言,数据类并没有什么特别之处,只是@dataclass装饰器自动生成__repr__,init,__eq__等一系列方法。为了帮助我们理解数据类,我们分别使用普通类和数据类来实现一个简单的类。
初始化
通常实现一个普通类时,我们需要实现初始化方法__init__,代码如下:
class Person:
__init__(self, name, age):
self.name = name
self.age = age
>>> one = Person("jim", 20)
>>> one.name
>>> "jim"
>>> one.age
>>> 20
而使用dataclass装饰器,我们不再需要实现__init__方法,装饰器dataclass负责生成初始化方法;同时,在使用装饰器dataclass我们可以为每个成员指定了类型,使代码的可读性更好。下面代码使用装饰器dataclass实现同样Person类。
@dataclass
class Person:
name:str
age:int
>>> one = Person("jim", 20)
>>> one.name
>>> "jim"
>>> one.age
>>> 20
另外,在定义数据类的时候我们还可以为每个属性指定缺省值。
@dataclass
class Person:
name:str = ""
age:int = 0
对象的表示
__repr__函数将对象转化为供解释器读取的形式。对于调试而言这样的输出并不是非常的有意义,例如:
class Person:
def __init__(self, name = "jim", age = 20):
self.name = name
self.age = age
>>> person = Person("jim", 20)
>>> person
>>> <__main__.Person object at 0x7ff395b3ccc5>
普通类可以实现__repr__ 方法来改变输出。
def __repr__(self):
return self.name
>>> person = Person(1)
>>> person
>>> "jim"
而对于dataclass类而言,__repr__函数会被自动生成,开发人员不在需要显示的实现该方法。例如:
@dataclass
class Person:
name: str
age: int
>>> person = Person("jim", 20)
>>> person
>>> Person(name = "jim", age = 20)
对象的比较
如果要让python对象支持比较,我们需要实现__eq__,__lt__等方法来支持比较。例如:
class Person:
def __init__(self, name = "jim", age = 20):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name
def __lt__(self, other):
return self.name < other.name
对于数据类而言,我们不再需要实现__eq__和__lt__等方法,在dataclass 修饰符中标明 order = True,这些比较方法会被自动实现。
@dataclass(order = True)
class Person:
name: str
age: int
但是自动生成的方法是如何比较两个对象的呢?数据类属性构成的元组,对元组进行比较,上面我们的Person数据类生成的比较方法如下。
def __eq__(self, other):
return (self.name, self.age) == ( other.name, other.age)
元组中元素的先后顺序是保持数据类中属性定义一致的。
定制dataclass行为
通过传递不同的参数给装饰器可以控制生成那些方法。dataclass完整参数列表以及默认值如下所示。
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
…
init: 默认是True,生成__init__函数,设置为False将禁止生成__init__方法。
repr : 默认是True,生成__repr__函数,设置为False将禁止生成__repr__方法。
eq: 默认是True,生成__eq__函数,设置为False将禁止生成__eq__方法。如果对未生成__eq__的对象进行比较,object对象的__eq__方法会被调用。 order : 默认为False,设置为True后会生成__gt__ , ge, lt, __le__方法。
frozen:默认情况下为False,如果设置为True数据对象就是一个只读对象。dataclass装饰器会为数据类生成__setattr__和__delattr__方法,试图修改对象时触发一个FrozenInstanceError。
unsafe_hash:默认值是False,将根据eq和frozen的设置情况来决定是否生成__hash__方法。
初始化后处理(Post init processing)
数据类自动生成__init__方法,但是这也损失了一些灵活性。例如,当我们的类完成初始化后需要做一些特定逻辑的情况。我们举一个简单的例子,假如我们需要在对象创建后打印一条log。
class Person:
def __init__(self, name = "jim", age = 20):
self.name = name
self.age = age
print("Person is created")
幸运的是初始化后已经被考虑到了,自动生成的__init__方法在执行完成后会调用__post_init__方法。所有的初始化可以放到这个函数里面。
@dataclass
class Person:
name: str
age: int
def __post_init__(self):
print("Person is created")
继承
数据类可以像普通类一样继承,通过继承子类便具有父类定义的属性。
@dataclass
class Person:
name: str
age: int
@dataclass
class Student(Person):
grade: int
>>> student = Student("Jim", 20, 100)
>>> student.name
>>> "Jim"
>>> student.age
>>> 20
>>> student.grade
>>> 100
在继承的情况下__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___被调用。如果我们需要调用A的___post_init__该怎么办了?因为它是父类的一个函数,所以我们可以使用关键字super来调用。
@dataclass
class B(A):
b: int
def __post_init__(self):
super().__post_init__()
print("B")
>>> a = B(1,2)
>>> A
B