Python3.7 dataclass使用指南小结


Posted in Python onFebruary 22, 2019

dataclass简介

dataclass的定义位于PEP-557,根据定义一个dataclass是指“一个带有默认值的可变的namedtuple”,广义的定义就是有一个类,它的属性均可公开访问,可以带有默认值并能被修改,而且类中含有与这些属性相关的类方法,那么这个类就可以称为dataclass,再通俗点讲,dataclass就是一个含有数据及操作数据方法的容器。

乍一看可能会觉得这个概念不就是普通的class么,然而还是有几处不同:

1.相比普通class,dataclass通常不包含私有属性,数据可以直接访问
2.dataclass的repr方法通常有固定格式,会打印出类型名以及属性名和它的值
3.dataclass拥有__eq__和__hash__魔法方法
4.dataclass有着模式单一固定的构造方式,或是需要重载运算符,而普通class通常无需这些工作

基于上述原因,通常自己实现一个dataclass是繁琐而无聊的,而dataclass单一固定的行为正适合程序为我们自动生成,于是dataclasses模块诞生了。

配合类型注解语法,我们可以轻松生成一个实现了__init__,__repr__,__cmp__等方法的dataclass:

from dataclasses import dataclass

@dataclass
class InventoryItem:
  '''Class for keeping track of an item in inventory.'''
  name: str
  unit_price: float
  quantity_on_hand: int = 0

  def total_cost(self) -> float:
    return self.unit_price * self.quantity_on_hand

同时使用dataclass也有一些好处,它比namedtuple更灵活。同时因为它是一个常规的类,所以你可以享受继承带来的便利。

dataclass的使用

我们分x步介绍dataclass的使用,首先是如何定义一个dataclass。

定义一个dataclass

dataclasses模块提供了一个装饰器帮助我们定义自己的数据类:

@dataclass
class Lang:
  """a dataclass that describes a programming language"""
  name: str = 'python'
  strong_type: bool = True
  static_type: bool = False
  age: int = 28

我们定义了一个描述某种程序语言特性的数据类——Lang,在接下来的例子中我们都会用到这个类。

在数据类被定义后,会根据给出的类型注解生成一个如下的初始函数:

def __init__(self, name: str='python',
      strong_type: bool=True,
      static_type: bool=False,
      age: int=28):
  self.name = name
  self.strong_type = strong_type
  self.static_type = static_type
  self.age = age

可以看到初始化操作都已经自动生成了,让我们试用一下:

>>> Lang()
Lang(name='python', strong_type=True, static_type=False, age=28)
>>> Lang('js', False, False, 23)
Lang(name='js', strong_type=False, static_type=False, age=23)
>>> Lang('js', False, False, 23) == Lang()
False
>>> Lang('python', True, False, 28) == Lang()
True

例子中可以看出__repr__和__eq__方法也已经为我们生成了,如果没有其他特殊要求的话这个dataclass已经具备了投入生产环境的能力,是不是很神奇?

深入dataclass装饰器

dataclass的魔力源泉都在dataclass这个装饰器中,如果想要完全掌控dataclass的话那么它是你必须了解的内容。

装饰器的原型如下:

dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

dataclass装饰器将根据类属性生成数据类和数据类需要的方法。

我们的关注点集中在它的kwargs上:

key 含义
init 指定是否自动生成__init__,如果已经有定义同名方法则忽略这个值,也就是指定为True也不会自动生成
repr 同init,指定是否自动生成__repr__;自动生成的打印格式为class_name(arrt1:value1, attr2:value2, ...)
eq 同init,指定是否生成__eq__;自动生成的方法将按属性在类内定义时的顺序逐个比较,全部的值相同才会返回True
order 自动生成__lt__,__le__,__gt__,__ge__,比较方式与eq相同;如果order指定为True而eq指定为False,将引发ValueError;如果已经定义同名函数,将引发TypeError
unsafehash 如果是False,将根据eq和frozen参数来生成__hash__: 1. eq和frozen都为True,__hash__将会生成 2. eq为True而frozen为False,__hash__被设为None 3. eq为False,frozen为True,__hash__将使用超类(object)的同名属性(通常就是基于对象id的hash) 当设置为True时将会根据类属性自动生成__hash__,然而这是不安全的,因为这些属性是默认可变的,这会导致hash的不一致,所以除非能保证对象属性不可随意改变,否则应该谨慎地设置该参数为True
frozen 设为True时对field赋值将会引发错误,对象将是不可变的,如果已经定义了__setattr__和__delattr__将会引发TypeError

有默认值的属性必须定义在没有默认值的属性之后,和对kw参数的要求一样。

上面我们偶尔提到了field的概念,我们所说的数据类属性,数据属性实际上都是被field的对象,它代表着一个数据的实体和它的元信息,下面我们了解一下dataclasses.field。

数据类的基石——dataclasses.field

先看下field的原型:

dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)

通常我们无需直接使用,装饰器会根据我们给出的类型注解自动生成field,但有时候我们也需要定制这一过程,这时dataclasses.field就显得格外有用了。

default和default_factory参数将会影响默认值的产生,它们的默认值都是None,意思是调用时如果为指定则产生一个为None的值。其中default是field的默认值,而default_factory控制如何产生值,它接收一个无参数或者全是默认参数的callable对象,然后用调用这个对象获得field的初始值,之后再将default(如果值不是MISSING)复制给callable返回的这个对象。

举个例子,对于list,当复制它时只是复制了一份引用,所以像dataclass里那样直接复制给实例的做法的危险而错误的,为了保证使用list时的安全性,应该这样做:

@dataclass
class C:
  mylist: List[int] = field(default_factory=list)

当初始化C的实例时就会调用list()而不是直接复制一份list的引用:

>>> c1 = C()
>>> c1.mylist += [1,2,3]
>>> c1.mylist
[1, 2, 3]
>>> c2 = C()
>>> c2.mylist
[]

数据污染得到了避免。

init参数如果设置为False,表示不为这个field生成初始化操作,dataclass提供了hook—— __post_init__供我们利用这一特性:

@dataclass
class C:
  a: int
  b: int
  c: int = field(init=False)

  def __post_init__(self):
    self.c = self.a + self.b

__post_init__在__init__后被调用,我们可以在这里初始化那些需要前置条件的field。

repr参数表示该field是否被包含进repr的输出,compare和hash参数表示field是否参与比较和计算hash值。metadata不被dataclass自身使用,通常让第三方组件从中获取某些元信息时才使用,所以我们不需要使用这一参数。

如果指定一个field的类型注解为dataclasses.InitVar,那么这个field将只会在初始化过程中(__init__和__post_init__)可以被使用,当初始化完成后访问该field会返回一个dataclasses.Field对象而不是field原本的值,也就是该field不再是一个可访问的数据对象。举个例子,比如一个由数据库对象,它只需要在初始化的过程中被访问:

@dataclass
class C:
  i: int
  j: int = None
  database: InitVar[DatabaseType] = None

  def __post_init__(self, database):
    if self.j is None and database is not None:
      self.j = database.lookup('j')

c = C(10, database=my_database)

这个例子中会返回c.i和c.j的数据,但是不会返回c.database的。

一些常用函数

dataclasses模块中提供了一些常用函数供我们处理数据类。

使用dataclasses.asdict和dataclasses.astuple我们可以把数据类实例中的数据转换成字典或者元组:

>>> from dataclasses import asdict, astuple
>>> asdict(Lang())
{'name': 'python', 'strong_type': True, 'static_type': False, 'age': 28}
>>> astuple(Lang())
('python', True, False, 28)

使用dataclasses.is_dataclass可以判断一个类或实例对象是否是数据类:

>>> from dataclasses import is_dataclass
>>> is_dataclass(Lang)
True
>>> is_dataclass(Lang())
True

dataclass继承

python3.7引入dataclass的一大原因就在于相比namedtuple,dataclass可以享受继承带来的便利。

dataclass装饰器会检查当前class的所有基类,如果发现一个dataclass,就会把它的字段按顺序添加进当前的class,随后再处理当前class的field。所有生成的方法也将按照这一过程处理,因此如果子类中的field与基类同名,那么子类将会无条件覆盖基类。子类将会根据所有的field重新生成一个构造函数,并在其中初始化基类。

看个例子:

@dataclass
class Python(Lang):
  tab_size: int = 4
  is_script: bool = True

>>> Python()
Python(name='python', strong_type=True, static_type=False, age=28, tab_size=4, is_script=True)

@dataclass
class Base:
  x: float = 25.0
  y: int = 0

@dataclass
class C(Base):
  z: int = 10
  x: int = 15

>>> C()
C(x=15, y=0, z=10)

Lang的field被Python继承了,而C中的x则覆盖了Base中的定义。

没错,数据类的继承就是这么简单。

总结

合理使用dataclass将会大大减轻开发中的负担,将我们从大量的重复劳动中解放出来,这既是dataclass的魅力,不过魅力的背后也总是有陷阱相伴,最后我想提几点注意事项:

  • dataclass通常情况下是unhashable的,因为默认生成的__hash__是None,所以不能用来做字典的key,如果有这种需求,那么应该指定你的数据类为frozen dataclass
  • 小心当你定义了和dataclass生成的同名方法时会引发的问题
  • 当使用可变类型(如list)时,应该考虑使用field的default_factory
  • 数据类的属性都是公开的,如果你有属性只需要初始化时使用而不需要在其他时候被访问,请使用dataclasses.InitVar

只要避开这些陷阱,dataclass一定能成为提高生产力的利器。

参考

https://docs.python.org/3.7/library/dataclasses.html

https://www.python.org/dev/peps/pep-0557

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python的dict字典结构操作方法学习笔记
May 07 Python
Python随机生成手机号、数字的方法详解
Jul 21 Python
python监控linux内存并写入mongodb(推荐)
Sep 11 Python
使用python爬虫实现网络股票信息爬取的demo
Jan 05 Python
Python实现简单的文本相似度分析操作详解
Jun 16 Python
基于DataFrame改变列类型的方法
Jul 25 Python
python的中异常处理机制
Aug 30 Python
解决pycharm的Python console不能调试当前程序的问题
Jan 20 Python
Django使用uwsgi部署时的配置以及django日志文件的处理方法
Aug 30 Python
python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例
Feb 26 Python
Python基础教程(一)——Windows搭建开发Python开发环境
Jul 20 Python
什么是Python包的循环导入
Sep 08 Python
Python文件读写常见用法总结
Feb 22 #Python
Python 通过requests实现腾讯新闻抓取爬虫的方法
Feb 22 #Python
解决python3.5 正常安装 却不能直接使用Tkinter包的问题
Feb 22 #Python
浅谈python3.6的tkinter运行问题
Feb 22 #Python
Scrapy框架爬取西刺代理网免费高匿代理的实现代码
Feb 22 #Python
在Python运行时动态查看进程内部信息的方法
Feb 22 #Python
Python开启线程,在函数中开线程的实例
Feb 22 #Python
You might like
如何隐藏你的.php文件
2007/01/04 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-3 代码组织和重用2
2011/07/03 PHP
php获取网站根目录物理路径的几种方法(推荐)
2017/03/04 PHP
thinkphp5.0自定义验证规则使用方法
2017/11/16 PHP
js文本框走动跑马灯效果代码分享
2015/08/25 Javascript
浅谈js中对象的使用
2016/08/11 Javascript
jqGrid表格底部汇总、合计行footerrow处理
2019/08/21 Javascript
Vue之Mixins(混入)的使用方法
2019/09/24 Javascript
vue从零实现一个消息通知组件的方法详解
2020/03/16 Javascript
详解微信小程序工程化探索之webpack实战
2020/04/20 Javascript
jquery轮播图插件使用方法详解
2020/07/31 jQuery
基于ant design日期控件使用_仅月份的操作
2020/10/27 Javascript
Python单元测试框架unittest简明使用实例
2015/04/13 Python
Python实现简单字典树的方法
2016/04/29 Python
剖析Python的Twisted框架的核心特性
2016/05/25 Python
Python中进程和线程的区别详解
2017/10/29 Python
Python实现统计给定字符串中重复模式最高子串功能示例
2018/05/16 Python
python logging重复记录日志问题的解决方法
2018/07/12 Python
python3实现网络爬虫之BeautifulSoup使用详解
2018/12/19 Python
Python常见数据类型转换操作示例
2019/05/08 Python
python 实现在一张图中绘制一个小的子图方法
2019/07/07 Python
python实现windows倒计时锁屏功能
2019/07/30 Python
tensorflow的ckpt及pb模型持久化方式及转化详解
2020/02/12 Python
为什么相对PHP黑python的更少
2020/06/21 Python
django rest framework 过滤时间操作
2020/07/12 Python
Django与AJAX实现网页动态数据显示的示例代码
2021/02/24 Python
CSS3中的常用选择器使用示例整理
2016/06/13 HTML / CSS
浅析移动设备HTML5页面布局
2015/12/01 HTML / CSS
美国一家著名的手表在线折扣网站:Discount Watch Store
2020/02/24 全球购物
校本教研工作制度
2014/01/22 职场文书
小区消防演习方案
2014/02/21 职场文书
保健品市场营销方案
2014/03/31 职场文书
竞选班干部演讲稿600字
2014/08/20 职场文书
2014年党的群众路线活动个人整改措施
2014/10/28 职场文书
Nginx配置并兼容HTTP实现代码解析
2021/03/31 Servers
vue2实现provide inject传递响应式
2021/05/21 Vue.js