Python3.7 dataclass 介绍

Posted on 2018年6月28日 by laixintao 1 Comment

Python3.7 加入了一个新的 module:dataclasses。可以简单的理解成“支持默认值、可以修改的tuple”( “mutable namedtuples with defaults”)。其实没什么特别的,就是你定义一个很普通的类,@dataclass 装饰器可以帮你生成 __repr__ __init__ 等等方法,就不用自己写一遍了。但是此装饰器返回的依然是一个 class,这意味着并没有带来任何不便,你依然可以使用继承、metaclass、docstring、定义方法等。

先展示一个 PEP 中举的例子,下面的这段代码(Python3.7):

1

2

3

4

5

6

7

8

9

@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 会自动生成

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0) -> None:

self.name = name

self.unit_price = unit_price

self.quantity_on_hand = quantity_on_hand

def __repr__(self):

return f‘InventoryItem(name={self.name!r}, unit_price={self.unit_price!r}, quantity_on_hand={self.quantity_on_hand!r})‘

def __eq__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) == (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

def __ne__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) != (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

def __lt__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) < (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

def __le__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) <= (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

def __gt__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) > (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

def __ge__(self, other):

if other.__class__ is self.__class__:

return (self.name, self.unit_price, self.quantity_on_hand) >= (other.name, other.unit_price, other.quantity_on_hand)

return NotImplemented

引入dataclass的理念

Python 想简单的定义一种容器,支持通过的对象属性进行访问。在这方面已经有很多尝试了:

  1. 标准库的 collections.namedtuple
  2. 标准库的 typing.NamedTuple
  3. 著名的 attr 库
  4. 各种 Snippet,问题和回答

那么为什么还需要 dataclass 呢?主要的好处有:

  1. 没有使用 BaseClass 或者 metaclass,不会影响代码的继承关系。被装饰的类依然是一个普通的类
  2. 使用类的 Fields 类型注解,用原生的方法支持类型检查,不侵入代码,不像 attr 这种库对代码有侵入性(要用 attr 的函数将一些东西处理)

dataclass 并不是要取代这些库,作为标准库的 dataclass 只是提供了一种更加方便使用的途径来定义 Data Class。以上这些库有不同的 feature,依然有存在的意义。

基本用法

dataclasses 的 dataclass 装饰器的原型如下:

1

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

很明显,这些默认参数可以控制是否生成魔术方法。通过本文开头的例子可以看出,不用加括号也可以调用。

通过 field 可以对参数做更多的定制化,比如默认值、是否参与repr、是否参与hash等。比如文档中的这个例子,由于 mylist 的缺失,就调用了 default_factory 。更多 field 能做的事情参考文档吧。

1

2

3

4

5

6

@dataclass

class C:

mylist: List[int] = field(default_factory=list)

c = C()

c.mylist += [1, 2, 3]

此外,dataclasses 模块还提供了很多有用的函数,可以将 dataclass 转换成 tuple、dict 等形式。话说我自己重复过很多这样的方法了……

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@dataclass

class Point:

x: int

y: int

@dataclass

class C:

mylist: List[Point]

p = Point(10, 20)

assert asdict(p) == {‘x‘: 10, ‘y‘: 20}

c = C([Point(0, 0), Point(10, 4)])

assert asdict(c) == {‘mylist‘: [{‘x‘: 0, ‘y‘: 0}, {‘x‘: 10, ‘y‘: 4}]}

Hook init

自动生成的 __init__ 可以被 hook。很简单,自动生成的 __init__ 方法会调用 __post_init__

1

2

3

4

5

6

7

8

@dataclass

class C:

a: float

b: float

c: float = field(init=False)

def __post_init__(self):

self.c = self.a + self.b

如果想传给 __post_init__ 方法但是不传给 __init__ ,可以使用一个特殊的类型 InitVar

1

2

3

4

5

6

7

8

9

10

11

@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)

不可修改的功能

Python 没有 const 类似的东西,理论上任何东西都是可以修改的。如果非要说不能修改的实现呢,这里有个比较著名的实现。只有不到10行代码。

但是有了 dataclass ,可以直接使用 @dataclass(frozen=True) 了。然后装饰器会对 Class 添加上 __setattr__ 和 __delattr__ 。Raise 一个 FrozenInstanceError。缺点是会有一些性能损失,因为 __init__ 必须通过 object.__setattr__ 。

继承

对于有继承关系的 dataclass,会按照 MRO 的反顺序(从object开始),对于每一个基类,将在基类找到的 fields 添加到顺序的一个 mapping 中。所有的基类都找完了,按照这个 mapping 生成所有的魔术方法。所以方法中这些参数的顺序,是按照找到的顺序排的,先找到的排在前面。因为是先找的基类,所以相同 name 的话,后面子类的 fields 定义会覆盖基类的。比如文档中的这个例子:

1

2

3

4

5

6

7

8

9

@dataclass

class Base:

x: Any = 15.0

y: int = 0

@dataclass

class C(Base):

z: int = 10

x: int = 15

那么最后生成的将会是:

1

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

注意 x y 的顺序是 Base 中的顺序,但是 C 的 x 是 int 类型,覆盖了 Base 中的 Any。

可变对象的陷阱

在前面的“基本用法”一节中,使用了 default_factory 。为什么不直接使用 [] 作为默认呢?

老鸟都会知道 Python 这么一个坑:将可变对象比如 list 作为函数的默认参数,那么这个参数会被缓存,导致意外的错误。详细的可以参考这里:Python Common Gotchas

考虑到下面的代码:

1

2

3

4

5

@dataclass

class D:

x: List = []

def add(self, element):

self.x += element

将会生成:

1

2

3

4

5

6

7

8

class D:

x = []

def __init__(self, x=x):

self.x = x

def add(self, element):

self.x += element

assert D().x is D().x

这样无论实例化多少对象,x 变量将在多个实例之间共享。dataclass 很难有一个比较好的办法预防这种情况。所以这个地方做的设计是:如果默认参数的类型是 list dict 或 set ,就抛出一个 TypeError。虽然不算完美,但是可以预防很大一部分情况了。

如果默认参数需要是 list,那么就用上面提到的 default_factory 。

原文地址:https://www.cnblogs.com/mapu/p/9340818.html

时间: 2024-10-14 19:33:52

Python3.7 dataclass 介绍的相关文章

Python3.7 dataclass使用指南

本文将带你走进python3.7的新特性dataclass,通过本文你将学会dataclass的使用并避免踏入某些陷阱. dataclass简介 dataclass的使用 定义一个dataclass 深入dataclass装饰器 数据类的基石--dataclasses.field 一些常用函数 dataclass继承 总结 dataclass简介 dataclass的定义位于PEP-557,根据定义一个dataclass是指"一个带有默认值的可变的namedtuple",广义的定义就是

Python3.6- Python基础介绍

1. 认识Python 1.1. Python发展历史 1.1.1. Python之父--吉多·范罗苏姆 Python的作者,Guido von Rossum(吉多·范罗苏姆),荷兰人.1982年,Guido从阿姆斯特丹大学获得了数学和计算机硕士学位.然而,尽管他算得上是一位数学家,但他更加享受计算机带来的乐趣.用他的话说,尽管拥有数学和计算机双料资质,他总趋向于做计算机相关的工作,并热衷于做任何和编程相关的活儿. 在那个时候,Guido接触并使用过诸如Pascal [?pæsk?l].C.Fo

简明Python3教程 1.介绍

Python是少有的几种既强大又简单的编程语言.你将惊喜地发现通过使用Python即可轻松专注于解决问题而非和你所用的语言格式与结构. 下面是Python的官方介绍: Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programm

Python3安装目录介绍

目录组织方式 关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构. 假设你的项目名为foo, 我比较建议的最方便快捷目录结构这样就足够了: Foo/ |-- bin/ |   |-- foo | |-- foo/ |   |-- tests/ |   |   |-- __init__.py |   |   |-- test_main.py |   | |   |-- __init__.py |   |-- main.py | |-- docs/ |   |-- con

python作业简单FTP(第七周)

作业需求: 1. 用户登陆 2. 上传/下载文件 3. 不同用户家目录不同 4. 查看当前目录下文件 5. 充分使用面向对象知识 思维图: 待补充()  思维分析: 1.用户登陆保存到文件对比用户名密码 2.上传用json序列化文件名,文件路径,文件大小传给服务器端.根据得到的字段内容操作上传动作 3.下载代码和上传基本可以互换,因为文件名都一样所以传一个文件大小即可 4.查看当前目录下文件,调用cd命令,既然能分解get 和put动作就可以看cd动作 5.添加了LINUX和Windows不同系

Python 程序:学员管理系统

Python 程序:学员管理系统 1.需求 2.表结构 3.readme 4.目录结构 5.代码 6.测试样图 一.需求 需求: 角色,讲师\学员, 用户登陆后根据角色不同,能做的事情不同,分别如下 讲师视图: 管理班级,可创建班级,根据学员qq号把学员加入班级 可创建指定班级的上课纪录,注意一节上课纪录对应多条学员的上课纪录, 即每节课都有整班学员上, 为了纪录每位学员的学习成绩,需在创建每节上课纪录是,同时为这个班的每位学员创建一条上课纪录 为学员批改成绩, 一条一条的手动修改成绩 学员视图

Python3 基础 —— 模块 Module 介绍

1.模块的作用 在交互模式下输出的变量和函数定义,一旦终端重启后,这些定义就都不存在了,为了持久保存这些变量.函数等的定义,Python中引入了模块(Module)的概念.一个Python模块其实就是一个脚本文件,具有后缀".py",例如 hello.py 就是一个模块文件名,和普通文件一样可以被永久保存在本地存储磁盘中. 2.模块的内容 Python模块中存放的是一些程序代码,例如,变量定义.函数定义或是代码语句.下面是hello.py模块的内容,其中有一个变量 a,一个函数 fun

Urllib.request用法简单介绍(Python3.3)

Urllib.request用法简单介绍(Python3.3),有需要的朋友可以参考下. urllib是Python标准库的一部分,包含urllib.request,urllib.error,urllib.parse,urlli.robotparser四个子模块,这里主要介绍urllib.request的一些简单用法. 首先是urlopen函数,用于打开一个URL: # -*- coding:utf-8 -*- #获取并打印google首页的htmlimport urllib.requestre

&lt;转&gt;Python3.x和Python2.x的区别介绍

1.性能Py3.0运行 pystone benchmark的速度比Py2.5慢30%.Guido认为Py3.0有极大的优化空间,在字符串和整形操作上可以取得很好的优化结果.Py3.1性能比Py2.5慢15%,还有很大的提升空间. 2.编码Py3.X源码文件默认使用utf-8编码,这就使得以下代码是合法的:>>> 中国 = 'china'>>>print(中国)china 3. 语法1)去除了<>,全部改用!=2)去除``,全部改用repr()3)关键词加入a