Dataclass是Python3.7新增的对象类型,若是你尚未使用Python3.7——这是最新发布的Python版本,请尽快到官方网站下载安装,一边随本文一块儿体会它的新发展。 简介html
Dataclass是Python的类,但适合存储数据对象。数据对象是什么?下面列出这种对象类型的几项特征,虽然不全面:python
固然还有更多的特性,可是这里列出的足以帮助你理解“数据对象”关键所在。bash
为了理解Dataclass类,咱们将写一个简单的类,它包含一个数字,而且容许咱们执行上面提到的操做。框架
Python 3.7提供了一个装饰器dataclass,用它能够将一个类转换为“数据对象”的类,即dataclass。dom
from dataclasses import dataclass
@dataclass
class A:
pass
复制代码
下面,对这种类型进行深刻研究。机器学习
下面建立一个类,注意初始化方法,主要是实现以该对象属性存储数字。函数
>>> class Number:
... def __init__(self, val):
... self.val = val
...
>>> one = Number(1)
>>> one.val
1
复制代码
下面使用@dataclass装饰器实现以上一样的功能:post
>>> @dataclass
... class Number:
... val:int
...
>>> done = Number(1)
>>> done.val
1
复制代码
概括使用dataclass装饰器带来的变化:学习
1.不须要在__init__
方法中给实例self的属性赋值。网站
2.有了类型提示,提升了可读性。如今咱们当即知道属性val是整数型的——彷佛在吸取强类型语言的特征。
若是还记得《Python之禅》中说过的“Readability counts”(重在可读性),彷佛感受上面的作法吻合了这种要求。
还能够这样写,设置默认值。
>>> @dataclass
... class Number:
... val:int = 0
...
>>> do = Number()
>>> do.val
0
>>> dt = Number(2)
>>> dt.val
2
复制代码
对象说明应该是用有意义的字符串表示的,它在在程序调试中很是有用。
默认的Python对象表示法不是颇有意义:
>>> class Number:
... def __init__(self, val=0):
... self.val = val
...
>>> a = Number()
>>> a
<__main__.Number object at 0x10279bf60>
复制代码
返回值没有用有意义的字符串表示,咱们仅仅可以知道它在内存中的地址。
因此,若是你要自定义对象类型,最好要使用__repr__
方法,它是一个对解释器友好的方法(详见《跟老齐学Python:轻松入门》中的有关阐述),经过这个方法能够对此对象给予有意义的说明。
>>> class Number:
... def __init__(self, val=0):
... self.val = val
... def __repr__(self):
... return "your object is: " + str(self.val)
...
>>> a = Number()
>>> a
your object is: 0
复制代码
再来看调试结果,则告诉咱们,变量a引用的对象就是数字1——咱们获得一个有意义的对象说明:
若是不用上面的方式,而是使用@dataclass装饰器定义一个Dataclass类型的对象,则会自动添加一个__repr__
函数,这样咱们就没必要手动实现它。
>>> @dataclass
... class Number:
... val:int = 0
...
>>> a = Number()
>>> a
Number(val=0)
复制代码
通常来讲,数据对象须要相互比较。
两个变量a和b所引用的对象,能够进行以下各类比较操做:
a < b
a > b
a == b
a >= b
a <= b
复制代码
在Python中,若是自定义对象类型实现上述各项比较功能,就必须定义相应的方法,好比要实现“==”和“<”比较,就要分别实现“__eq__
”和“__lt__
”两个特殊方法(详见《跟老齐学Python:轻松入门》中的说明)。下面是一个简单的例子:
class Number:
def __init__( self, val = 0):
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
复制代码
就这么多代码,惊讶了吧。
不须要定义__eq__
和__lt__
方法。只须要在@dataclass装饰器的参数中设置order = True
便可,就自动在所定义的类中实现了这两个特殊方法。
那么,怎么作到的呢?
当你使用@dataclass时,它会向类定义中添加函数__eq__
和__lt__
。这是咱们已经知道的。那么,这些函数是如何知道检查是否相等和进行比较的呢?
一个由dataclass生成的__eq__
函数将把属性元组与相同类的另外一个实例的属性的元组进行比较。在咱们的例子中,由__eq__
方法自动生成的对象将等同于:
def __eq__(self, other):
return (self.val,) == (other.val,)
复制代码
让咱们来看一个更详细的例子:
写一个数据类Person来保存姓名和年龄。
@dataclass(order = True)
class Person:
name: str
age:int = 0
复制代码
这个自动生成的__eq__
方法将等价于:
def __eq__(self, other):
return (self.name, self.age) == ( other.name, other.age)
复制代码
注意属性的顺序。它们老是按照你在数据类中定义的顺序生成的。
同理,等效的__le__
函数将相似于这样:
def __le__(self, other):
return (self.name, self.age) <= (other.name, other.age)
复制代码
由于数据对象中已经默认实现了各类比较功能,所以就能够实现排序。
>>> import random
>>> a = [Number(random.randint(1,10)) for _ in range(10)] #随机数列表
>>> a
>>> [Number(val=2), Number(val=7), Number(val=6), Number(val=5), Number(val=10), Number(val=9), Number(val=1), Number(val=10), Number(val=1), Number(val=7)]
>>> sorted_a = sorted(a) #Sort Numbers in ascending order
>>> [Number(val=1), Number(val=1), Number(val=2), Number(val=5), Number(val=6), Number(val=7), Number(val=7), Number(val=9), Number(val=10), Number(val=10)]
>>> reverse_sorted_a = sorted(a, reverse = True) #降序排列
>>> reverse_sorted_a
>>> [Number(val=10), Number(val=10), Number(val=9), Number(val=7), Number(val=7), Number(val=6), Number(val=5), Number(val=2), Number(val=1), Number(val=1)]
复制代码
在Python中,自定义不可变对象,其实有点麻烦。
>>> class Number:
... val: int = 0
...
>>> a = Number()
>>> a.val
0
>>> a.val = 10 #所谓不变对象,就是当用这种方式修改属性值的时候,应该不容许,应该报异常
>>> a.val #固然,在这里没有报异常,而是实现了修改,由于此处的对象不是不可变的,是可变的。
10
复制代码
在Python3.7中,有了dataclass,则能够很轻松的实现上述设想。
在下面的代码中,使用了@dataclass装饰器,实现不可变对象。
>>> from dataclasses import dataclass
>>> @dataclass(frozen=True)
... class Book:
... name: str = "Learn Python with Laoqi"
...
>>> python_book = Book()
>>> python_book.name
'Learn Python with Laoqi'
>>> python_book.name = "other Python Book" #试图进行修改,结果报异常,不容许修改
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'name'
复制代码
用这种方式设置常熟,是否是更优雅。
__post_init__
进行初始化后处理什么是初始化后处理?下面举一个例子,说明之:
>>> 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.5)
>>> a.decimal
0.5
>>> a.integer
2.0
复制代码
在类Float中,定义了方法process,而且在初始化方法__init__
中调用,这就是要在初始化以后作的事情。
在Python3.7中,具备上述功能代码可使用__post_init__
来完成。以下所示:
>>> @dataclass
... class FloatNumber:
... val: float = 0.0
... def __post_init__(self):
... self.decimal, self.integer = math.modf(self.val)
...
>>> b = FloatNumber(2.5)
>>> b.val
2.5
>>> b.integer
2.0
>>> b.decimal
0.5
复制代码
是否是再次感觉到了“代码的整洁”。
参考资料:
https://medium.com/mindorks/understanding-python-dataclasses-part-1-c3ccd4355c34
https://docs.python.org/3/library/dataclasses.html
复制代码
以上书籍,各大网店有售。相关网站:itdiffer.com