Python3.7中的Dataclass

Dataclass是Python3.7新增的对象类型,若是你尚未使用Python3.7——这是最新发布的Python版本,请尽快到官方网站下载安装,一边随本文一块儿体会它的新发展。 简介html

Dataclass是Python的类,但适合存储数据对象。数据对象是什么?下面列出这种对象类型的几项特征,虽然不全面:python

  • 它们存储数据并表示某种数据类型,例如:数字。对于熟悉ORM的朋友来讲(若是不熟悉,请参阅《跟老齐学Python:Django实战》中的讲述),数据模型实例就是一个数据对象。它表明了一种特定的实体。它所具备的属性定义或表示了该实体。
  • 它们能够与同一类型的其余对象进行比较。例如:大于、小于或等于。

固然还有更多的特性,可是这里列出的足以帮助你理解“数据对象”关键所在。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
复制代码

  • 《跟老齐学Python:轻松入门》:面向初学Python的读物,深刻浅出地讲解Python3基础知识
  • 《跟老齐学Python:Django实战》:是Python在网站开发方面的书籍,以项目的方式介绍Django框架的应用方式
  • 《跟老齐学Python:数据分析》:是Python在数据分析、机器学习方面的基础读物,重点介绍Numpy、Pandas的有关知识和数据可视化的实现方法。

以上书籍,各大网店有售。相关网站:itdiffer.com

相关文章
相关标签/搜索