概要
整体上来讲 Python 这门语言还是比较注重开发者体验的,一方面表现在其简洁的语法上,另一方面就是其丰富的标准库模块。Python-3.7 版本为标准库引入了一个新的模块 dataclasses ,用这个新模块可以让我们本就简洁的代码再简短不少。
传统的写法
以前代码要一行一行的码,处处要体现工匠精神,这东西马虎不得;一不小心就会出 Bug ,后来“声明式”的编程思想,得以实践我们只要说我想要什么效果就行。 在正式讲“声明式”这个新东西之前,我们还是来看一下传统的代码怎么写。
#!/usr/bin/evn python3
class Person(object):
def __init__(self,first_name,last_name,age):
"""
Parameters
----------
first_name: str
名字
last_name: str
姓氏
age: int
年龄
"""
self.first_name = first_name
self.last_name = last_name
self.age = age
def full_name(self):
"""返回实例对象的全名
Return
------
str
"""
return self.last_name + self.first_name
def __str__(self):
return f"{self.__class__.__name__}(first_name='{self.first_name}',last_name='{self.last_name}',age={self.age})"
if __name__ == "__main__":
zhang_san = Person("三","张",18)
print(zhang_san.full_name())
print(zhang_san)
这个可以说得上是一五一十,一丝不苟了。问题不大就是比较费人力,不知道我们在多少类中重复如下样板代码。
def __init__(self, a, b):
self.a = a
self.b = b
dataclasses 这个新模块可以把我们从这种重得的劳动中解放出来。
改进 __init__ 样板代码
如果 __init__ 只是为了简单的给对象属性赋值,那 dataclasses 会是我们最好的选择;我们只要声明一下就行了,下面直接看代码。
#!/usr/bin/evn python3
from dataclasses import dataclass
@dataclass
class Person(object):
first_name: str
last_name: str
age: int
def full_name(self):
"""返回实例对象的全名
Return
------
str
"""
return self.last_name + self.first_name
if __name__ == "__main__":
zhang_san = Person("三","张",18)
print(zhang_san.full_name())
print(zhang_san)
代码在以肉眼可见的方式在变短,而我们要做的就是声明一下属性名和对应的类型就行;实践中我还发现声明式的另一个好处,就是可以防止一些拼写错误。
尝试介入对象的构造过程
前面我们的代码都是为了说明, dataclasses 是一个声明式的模块,我们只要声明自己想要做么就行了;有了招式少了心法。心法就是用来回答 “那 dataclasses 是怎么做到的?” 。
答案是它通过元编程深入的介入对象的构造过程,帮我们实现了一些通用的代码,比如上文说的 __init__ 方法。
另外它还给我们暴露出了一些生命周期钩子,让使用它的人不用了解 Python 元编程也能玩的飞起。现在以我们来解决一下之前留下的一个坑 full_name 函数,这个 full_name 看起来是一个名词但它实际上是一个函数,真的是让人迷惑呢!
传统解法 1 @property 来处理
class Person(object):
# 省略其它代码
# 用属性语法
@property
def full_name(self):
"""返回实例对象的全名
Return
------
str
"""
return self.last_name + self.first_name
传统解法 2 直接保存成一个新的属性
class Person(object):
"""
"""
def __init__(self,first_name,last_name,age):
"""
Parameters
----------
first_name: str
名字
last_name: str
姓氏
age: int
年龄
"""
self.first_name = first_name
self.last_name = last_name
self.age = age
# 对于这种相当简单的逻辑我们直接写在 __init__ 钩子里也没有问题
# 直接搞成属性
self.full_name = self.last_name + self.first_name
好!终于写到这里了。上面的代码实际上就是在用已有的 first_name & last_name 再创建新属性;还记得吗? dataclasses 介入了对象的构造过程,对于这种后置的操作 dataclasses 也给我们暴露了钩子。下面看代码
#!/usr/bin/evn python3
from dataclasses import asdict, dataclass
@dataclass
class Person(object):
first_name: str
last_name: str
age: int
def __post_init__(self):
self.full_name = self.last_name + self.first_name
if __name__ == "__main__":
zhang_san = Person("三","张",18)
print(zhang_san.full_name)
好的,写到了这里我们基本上讲清理了 dataclasses 的“招式”和“心法”。理解了心法就可以越用越野了。
前面说 dataclasses 说是为了减少代码量,我怎么没有看到第二个例子有减少多少代码呀!那原理(心法)的事我们就点到为止了,还是来看一下在实战中它能帮我们少写多少代码。
对象转换为字典
以前总有哪么一些场景要把对象转换成字典,这些重复的代码真的写的让人想吐。
class Person(object):
"""
"""
#
# 省略其它代码
#
def asdict(self):
"""返回对象的字典形式
Return
------
dict
"""
return {
'first_name':self.first_name,
'last_name': self.last_name,
'age': self.age
}
if __name__ == "__main__":
zhang_san = Person("三","张",18)
print(zhang_san.asdict())
现在好了,对象是 dataclasses 帮忙我们构造的,那它一定知道对象有哪些属性!就是因为它有这个信息,它实现了一个通用的转字典的逻辑。并且这个非常方便,不用多写一行代码。
#!/usr/bin/evn python3
from dataclasses import asdict, dataclass
@dataclass
class Person(object):
first_name: str
last_name: str
age: int
if __name__ == "__main__":
zhang_san = Person("三","张",18)
print(asdict(zhang_san))
如果想节约点内存,想把对象转换为 tuple 也是一样的简单,它提供了一个 astuple 方法。
安全
以前大家在评价一个 C++ 工程师能力怎么样的时候,总结出了一条比较简单的标准,“看他 const 用的怎么样”。C++ 的 const 讲法可多了去了,有安全、性能、API 是否优雅 ...。今天我们就谈一下 dataclasses 在安全上的应用场景。
假如我们要实现一个功能,“对象一旦创建完成之后就不能更新它的属性,也就是说这个对象是只读的。” 在 dataclasses 还没有出现之后这些还要一些元编程的知识才能写出来。传统的代码和下面这段差不多。
class Person(object):
"""
"""
def __init__(self,first_name,last_name,age):
"""
Parameters
----------
first_name: str
名字
last_name: str
姓氏
age: int
年龄
"""
self.first_name = first_name
self.last_name = last_name
self.age = age
# 第一步我们要在初始化完成之后打上标记
self._is_inited = True
# 第二步 检查对于非初始化赋值的情况,我们要报错。
def __setattr__(self,attr,value):
if '_is_inited' in self.__dict__:
raise RuntimeError("read only object .")
else:
object.__setattr__(self,attr,value)
if __name__ == "__main__":
zhang_san = Person("三","张",18)
zhang_san.first_name="四"
由于运行的时候改了对象的属性,所以它会报错,详细的报错如下。
python3 main.py
Traceback (most recent call last):
File "/private/tmp/pys/main.py", line 82, in <module>
zhang_san.first_name="四"
File "/private/tmp/pys/main.py", line 72, in __setattr__
raise RuntimeError("read only object .")
RuntimeError: read only object .
如果使用的是 dataclasses 来构造的对象就完全不用写这么多代码,还什么“Python 元编程”,滚开!
#!/usr/bin/evn python3
@dataclass(frozen=True)
class Person(object):
first_name: str
last_name: str
age: int
if __name__ == "__main__":
zhang_san = Person("三","张",18)
zhang_san.first_name="四"
print(zhang_san)
同样可以做到只读。
python3 main.py
Traceback (most recent call last):
File "/private/tmp/pys/main.py", line 78, in <module>
zhang_san.first_name="四"
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'first_name'
结尾
时代在进步,就算是编程这种与机器打交道的事,也是越来越以人为本了。以前我总是说 “用 Python 语言,import 完了之后,差不多就完成了 50% ” 现在看来已经完成了 60% 。
dataclasses 是一个比较大的模块功能多了去了,大家看去看吧。“分享” + “收藏” + “点赞” + “在看” 这些只会让我更新的更快。