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 。

Python3.7 dataclass 介绍的更多相关文章

  1. Python3.7 dataclass使用指南

    本文将带你走进python3.7的新特性dataclass,通过本文你将学会dataclass的使用并避免踏入某些陷阱. dataclass简介 dataclass的使用 定义一个dataclass ...

  2. 简明Python3教程 1.介绍

    Python是少有的几种既强大又简单的编程语言.你将惊喜地发现通过使用Python即可轻松专注于解决问题而非和你所用的语言格式与结构. 下面是Python的官方介绍: Python is an eas ...

  3. 在python3.6下 发明一个类似python3.7 dataclass数据类,不用在 __init__中self.xx

    虽然我用3.6,但我在2.7转3.6时候,把3.3 3.4 3.5 3.6的变化都看了一次,虽然已经忘了哪些变化.同时也关注3.7 3.8的变化,3.7中就有1个数据类印象深刻,因为之前在定义这种类时 ...

  4. 简明Python3教程 3.介绍

    介绍 Python是少有的几种既强大又简单的编程语言.你将惊喜地发现通过使用Python即可轻松专注于解决问题而非和你所用的语言格式与结构. 下面是Python的官方介绍: Python is an ...

  5. python3.8 := and python3.7 dataclass

    代码示例 from dataclasses import field,dataclass @dataclass class People: name :str =field(init="张三 ...

  6. Python3安装目录介绍

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

  7. Python3学习笔记 - 准备环境

    前言 最近乘着项目不忙想赶一波时髦学习一下Python3.由于正好学习了Docker,并深深迷上了Docker,所以必须趁热打铁的用它来创建我们的Python3的开发测试环境.Python3的中文教程 ...

  8. Python2/3中的urllib库

    urllib库对照速查表 Python2.X Python3.X urllib urllib.request, urllib.error, urllib.parse urllib2 urllib.re ...

  9. Linux下安装3.0以上的python

    Linux下自带的python2.7是不建议删除的,很多系统软件依赖python2.7,但是现在我们学习python一般需要python3.0,下面介绍安装python3.0. 1.进入python官 ...

随机推荐

  1. 编译安装php5.6

    sudo yum -y install curl-devel  bzip2 bzip2-devel libxml2  libxml2-devel  libjpeg libpng freetype li ...

  2. Scrum冲刺阶段7

    成员今日完成的任务 人员 任务 何承华 美化会员查看安排界面 陈宇 后端设计 丁培辉 美化会员查看界面 温志铭 会员查看界面设计 杨宇潇 会员查看界面设计 张主强 服务器构建 成员遇到的问题 人员 问 ...

  3. 【Python】【BugList13】req = requests.get(url=target)报错: (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)')

    [代码] # -*- coding:UTF-8 -*- import requests if __name__ == '__main__': target = 'https://unsplash.co ...

  4. Spring-bean的自动装配

    bean的自动装配:可以让一个bean对象自动的引用其他bean byType:按照类型进行装配.  缺点:如果在IOC容器中存在多个类型相同的bean的时候,会出现异常. <bean id=& ...

  5. String Match

    Finding length of longest common substring /*Finding length of longest common substring using DP * * ...

  6. ThinkPhp5 出现访问出现 No input file specified. 问题

    今天复习一下ThinkPhp5,在官网下载了核心版,windows下配置了虚拟域名之后出现了神奇的现象 如下图 直接访问域名能访问到index模块下的index控制器下的index方法 但是我输入完整 ...

  7. 使用 Chrome 浏览器插件 Web Scraper 10分钟轻松实现网页数据的爬取

    web scraper 下载:Web-Scraper_v0.2.0.10 使用 Chrome 浏览器插件 Web Scraper 可以轻松实现网页数据的爬取,不写代码,鼠标操作,点哪爬哪,还不用考虑爬 ...

  8. vue学习_01

    一.什么是VUE 渐进式的前端框架,MVVM(Model,view,viewmodel)模式,饿了么用的就是vue框架 二.VUE基本语法 1.引入vue: <script src=" ...

  9. (转)MySql中监视增删改查和查看日志记录

    转载地址为:http://blog.51cto.com/hades02/1641652 首先在命令行输入 show global variables like '%general%' ,然后出现下面的 ...

  10. js之Ajax下载文件

    传统上,客户端将依靠浏览器来处理从服务器下载文件.然而,这种方法需要打开一个新的浏览器窗口,iframe或任何其他类型的不友好和黑客行为.为下载请求添加额外的头信息也很困难.更好的解决方案是使用HTM ...