一文带你看懂python dataclass装饰器

一文带你看懂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

相关推荐