这个 Python 库有必要好好学学
在很多情况下,我们会有把 Python 对象进行序列化或反序列化的需求,比如开发 REST API,比如一些面向对象化的数据加载和保存,都会应用到这个功能。
这里看一个最基本的例子,这里给到一个 User 的 Class 定义,再给到一个 data 数据,像这样:
class User(object):def __init__(self, name, age):self.name = nameself.age = agedata = [{'name': 'Germey','age': 23
}, {'name': 'Mike','age': 20
}]
现在我要把这个 data 快速转成 User 组成的数组,变成这样:
[User(name='Germey', age=23), User(name='Mike', age=20)]
你会怎么来实现?
或者我有了上面的列表内容,想要转成一个 JSON 字符串,变成这样:
[{"name": "Germey", "age": 23}, {"name": "Mike", "age": 20}]
你又会怎么操作呢?
另外如果 JSON 数据里面有各种各样的脏数据,你需要在初始化时验证这些字段是否合法,另外 User 这个对象里面 name、age 的数据类型不同,如何针对不同的数据类型进行针对性的类型转换,这个你有更好的实现方案吗?
初步思路
之前我写过一篇文章这可能是 Python 面向对象编程的最佳实践,介绍过 attrs 和 cattrs 这两个库,它们二者的组合可以非常方便地实现对象的序列化和反序列化。
譬如这样:
from attr import attrs, attrib
from cattr import structure, unstructure@attrs
class User(object):name = attrib()age = attrib()data = {'name': 'Germey','age': 23
}
user = structure(data, User)
print('user', user)
json = unstructure(user)
print('json', json)
运行结果:
user User(name='Germey', age=23)
json {'name': 'Germey', 'age': 23}
好,这里我们通过 attrs 和 cattrs 这两个库来实现了单个对象的转换。
首先我们要肯定一下 attrs 这个库,它可以极大地简化 Python 类的定义,同时每个字段可以定义多种数据类型。
但 cattrs 这个库就相对弱一些了,如果把 data 换成数组,用 cattrs 还是不怎么好转换的,另外它的 structure 和 unstructure 在某些情景下容错能力较差,所以对于上面的需求,用这两个库搭配起来并不是一个最优的解决方案。
另外数据的校验也是一个问题,attrs 虽然提供了 validator 的参数,但对于多种类型的数据处理的支持并没有那么强大。
所以,我们想要寻求一个更优的解决方案。
更优雅的方案
这里推荐一个库,叫做 marshmallow,它是专门用来支持 Python 对象和原生数据相互转换的库,如实现 object -> dict,objects -> list, string -> dict, string -> list 等的转换功能,另外它还提供了非常丰富的数据类型转换和校验 API,帮助我们快速实现数据的转换。
要使用 marshmallow 这个库,需要先安装下:
pip3 install marshmallow
好了之后,我们在之前的基础上定义一个 Schema,如下:
class UserSchema(Schema):name = fields.Str()age = fields.Integer()@post_loaddef make(self, data, **kwargs):return User(**data)
还是之前的数据:
data = [{'name': 'Germey','age': 23
}, {'name': 'Mike','age': 20
}]
这时候我们只需要调用 Schema 的 load 事件就好了:
schema = UserSchema()
users = schema.load(data, many=True)
print(users)
输出结果如下:
[User(name='Germey', age=23), User(name='Mike', age=20)]
这样,我们非常轻松地完成了 JSON 到 User List 的转换。
有人说,如果是单个数据怎么办呢,只需要把 load 方法的 many 参数去掉即可:
data = {'name': 'Germey','age': 23
}schema = UserSchema()
user = schema.load(data)
print(user)
输出结果:
User(name='Germey', age=23)
当然,这仅仅是一个反序列化操作,我们还可以正向进行序列化,以及使用各种各样的验证条件。
下面我们再来看看吧。
更方便的序列化
上面的例子我们实现了序列化操作,输出了 users 为:
[User(name='Germey', age=23), User(name='Mike', age=20)]
有了这个数据,我们也能轻松实现序列化操作。
序列化操作,使用 dump 方法即可
result = schema.dump(users, many=True)
print('result', result)
运行结果如下:
result [{'age': 23, 'name': 'Germey'}, {'age': 20, 'name': 'Mike'}]
由于是 List,所以 dump 方法需要加一个参数 many 为 True。
当然对于单个对象,直接使用 dump 同样是可以的:
result = schema.dump(user)
print('result', result)
运行结果如下:
result {'name': 'Germey', 'age': 23}
这样的话,单个、多个对象的序列化也不再是难事。
经过上面的操作,我们完成了 object 到 dict 或 list 的转换,即:
object <-> dict
objects <-> list
验证
当然,上面的功能其实并不足以让你觉得 marshmallow 有多么了不起,其实就是一个对象到基本数据的转换嘛。但肯定不止这些,marshmallow 还提供了更加强大啊功能,比如说验证,Validation。
比如这里我们将 age 这个字段设置为 hello,它无法被转换成数值类型,所以肯定会报错,样例如下:
data = {'name': 'Germey','age': 'hello'
}from marshmallow import ValidationError
try:schema = UserSchema()user, errors = schema.load(data)print(user, errors)
except ValidationError as e:print('e.message', e.messages)print('e.valid_data', e.valid_data)
这里如果加载报错,我们可以直接拿到 Error 的 messages 和 valid_data 对象,它包含了错误的信息和正确的字段结果,运行结果如下:
e.message {'age': ['Not a valid integer.']}
e.valid_data {'name': 'Germey'}
因此,比如我们想要开发一个功能,比如用户注册,表单信息就是提交过来的 data,我们只需要过一遍 Validation,就可以轻松得知哪些数据符合要求,哪些不符合要求,接着再进一步进行处理。
当然验证功能肯定不止这一些,我们再来感受一下另一个示例:
from pprint import pprint
from marshmallow import Schema, fields, validate, ValidationErrorclass UserSchema(Schema):name = fields.Str(validate=validate.Length(min=1))permission = fields.Str(validate=validate.OneOf(['read', 'write', 'admin']))age = fields.Int(validate=validate.Range(min=18, max=40))in_data = {'name': '', 'permission': 'invalid', 'age': 71}
try:UserSchema().load(in_data)
except ValidationError as err:pprint(err.messages)
比如这里的 validate 字段,我们分别校验了 name、permission、age 三个字段,校验方式各不相同。
如 name 我们要判断其最小值为 1,则使用了 Length 对象。permission 必须要是几个字符串之一,这里又使用了 OneOf 对象,age 又必须是介于某个范围之间,这里就使用了 Range 对象。
下面我们故意传入一些错误的数据,看下运行结果:
{'age': ['Must be greater than or equal to 18 and less than or equal to 40.'],'name': ['Shorter than minimum length 1.'],'permission': ['Must be one of: read, write, admin.']}
可以看到,这里也返回了数据验证的结果,对于不符合条件的字段,一一进行说明。
另外我们也可以自定义验证方法:
from marshmallow import Schema, fields, ValidationErrordef validate_quantity(n):if n < 0:raise ValidationError('Quantity must be greater than 0.')if n > 30:raise ValidationError('Quantity must not be greater than 30.')class ItemSchema(Schema):quantity = fields.Integer(validate=validate_quantity)in_data = {'quantity': 31}
try:result = ItemSchema().load(in_data)
except ValidationError as err:print(err.messages)
通过自定义方法,同样可以实现更灵活的验证,运行结果:
{'quantity': ['Quantity must not be greater than 30.']}
对于上面的例子,还有更优雅的写法:
from marshmallow import fields, Schema, validates, ValidationErrorclass ItemSchema(Schema):quantity = fields.Integer()@validates('quantity')def validate_quantity(self, value):if value < 0:raise ValidationError('Quantity must be greater than 0.')if value > 30:raise ValidationError('Quantity must not be greater than 30.')
通过定义方法并用 validates 修饰符,使得代码的书写更加简洁。
必填字段
如果要想定义必填字段,只需要在 fields 里面加入 required 参数并设置为 True 即可,另外我们还可以自定义错误信息,使用 error_messages 即可,例如:
from pprint import pprint
from marshmallow import Schema, fields, ValidationErrorclass UserSchema(Schema):name = fields.String(required=True)age = fields.Integer(required=True, error_messages={'required': 'Age is required.'})city = fields.String(required=True,error_messages={'required': {'message': 'City required', 'code': 400}},)email = fields.Email()try:result = UserSchema().load({'email': 'foo@bar.com'})
except ValidationError as err:pprint(err.messages)
默认字段
对于序列化和反序列化字段,marshmallow 还提供了默认值,而且区分得非常清楚!如 missing 则是在反序列化时自动填充的数据,default 则是在序列化时自动填充的数据。
例如:
from marshmallow import Schema, fields
import datetime as dt
import uuidclass UserSchema(Schema):id = fields.UUID(missing=uuid.uuid1)birthdate = fields.DateTime(default=dt.datetime(2017, 9, 29))print(UserSchema().load({}))
print(UserSchema().dump({}))
这里我们都是定义的空数据,分别进行序列化和反序列化,运行结果如下:
{'id': UUID('06aa384a-570c-11ea-9869-a0999b0d6843')}
{'birthdate': '2017-09-29T00:00:00'}
可以看到,在没有真实值的情况下,序列化和反序列化都是用了默认值。
这个真的是解决了我之前在 cattrs 序列化和反序列化时候的痛点啊!
指定属性名
在序列化时,Schema 对象会默认使用和自身定义相同的 fields 属性名,当然也可以自定义,如:
class UserSchema(Schema):name = fields.String()email_addr = fields.String(attribute='email')date_created = fields.DateTime(attribute='created_at')user = User('Keith', email='keith@stones.com')
ser = UserSchema()
result, errors = ser.dump(user)
pprint(result)
运行结果如下:
{'name': 'Keith','email_addr': 'keith@stones.com','date_created': '2014-08-17T14:58:57.600623+00:00'}
反序列化也是一样,例如:
class UserSchema(Schema):name = fields.String()email = fields.Email(load_from='emailAddress')data = {'name': 'Mike','emailAddress': 'foo@bar.com'
}
s = UserSchema()
result, errors = s.load(data)
运行结果如下:
{'name': u'Mike','email': 'foo@bar.com'}
嵌套属性
对于嵌套属性,marshmallow 当然也不在话下,这也是让我觉得 marshmallow 非常好用的地方,例如:
from datetime import date
from marshmallow import Schema, fields, pprintclass ArtistSchema(Schema):name = fields.Str()class AlbumSchema(Schema):title = fields.Str()release_date = fields.Date()artist = fields.Nested(ArtistSchema())bowie = dict(name='David Bowie')
album = dict(artist=bowie, title='Hunky Dory', release_date=date(1971, 12, 17))schema = AlbumSchema()
result = schema.dump(album)
pprint(result, indent=2)
这样我们就能充分利用好对象关联外键来方便地实现很多关联功能。
以上介绍的内容基本算在日常的使用中是够用了,当然以上都是一些基本的示例,对于更多功能,可以参考 marchmallow 的官方文档:https://marshmallow.readthedocs.io/en/stable/,强烈推荐大家用起来。
作者:华为云享专家 崔庆才静觅
这个 Python 库有必要好好学学相关推荐
- python那么多库怎么学_这个 Python 库有必要好好学学
" 阅读本文大概需要 6 分钟. " 在很多情况下,我们会有把 Python 对象进行序列化或反序列化的需求,比如开发 REST API,比如一些面向对象化的数据加载和保存,都会应 ...
- 华为python有必要学吗_【华为云技术分享】这个 Python 库有必要好好学学
这里看一个最基本的例子,这里给到一个 User 的 Class 定义,再给到一个 data 数据,像这样: 1 class User(object):2 def __init__(self, name ...
- 数据处理一条龙!这15个Python库不可不知
全文共3118字,预计学习时长13分钟 图源:searchenginejournal 如果你是一名数据科学家或数据分析师,或者只是对这一行业感兴趣,那下文中这些广受欢迎且非常实用的Python库你一定 ...
- 年薪20万Python工程师进阶(7):Python资源大全,让你相见恨晚的Python库
我是 环境管理 管理 Python 版本和环境的工具 pyenv – 简单的 Python 版本管理工具. Vex – 可以在虚拟环境中执行命令. virtualenv – 创建独立 Python 环 ...
- 53个Python库,你必须要试试
Python库大全 大邓将Python库整理为8部分,对每个库稍加自己的理解和评价,对Python感兴趣的同学可以收藏起来 •网络爬虫 •数据库 •数据分析 •机器学习 •可视化 •文本分析 •GUI ...
- Python库全部整理出来了,非常全面
库名称简介 Chardet 字符编码探测器,可以自动检测文本.网页.xml的编码. colorama 主要用来给文本添加各种颜色,并且非常简单易用. Prettytable 主要用于在终端或浏览器端构 ...
- Python工具 | 9个用来爬取网络站点的 Python 库
1️⃣Scrapy 一个开源和协作框架,用于从网站中提取所需的数据. 以快速,简单,可扩展的方式. 官网 2️⃣cola 一个分布式爬虫框架. GitHub 3️⃣Demiurge 基于 PyQuer ...
- 2021年最有用的数据清洗 Python 库
作者 | 周萝卜 来源 | 萝卜大杂烩 大多数调查表明,数据科学家和数据分析师需要花费 70-80% 的时间来清理和准备数据以进行分析. 对于许多数据工作者来说,数据的清理和准备也往往是他们工作中最不 ...
- 用于小型图形挖掘研究的瑞士军刀:空手道俱乐部的图表学习Python库
作者 | Benedek Rozemberczki 译者 | 天道酬勤 责编 | Carol 出品 | AI科技大本营(ID:rgznai100) 空手道俱乐部(Karate Club)是Networ ...
最新文章
- HTML5学习笔记(十八):闭包
- time.h 详细介绍
- SQL优化—— 大批量插入数据
- JVM源码—教你傻瓜式编译openjdk7
- oracle 使从表中随机取出一行记录数据
- winform 统计大量数据重复的元素个数_面试系列:十个海量数据处理方法大总结...
- MySQL “error C3646: 'fd': 未知重写说明符”
- java 阻塞 wait_Java面试基础问题之(二十一)—— 线程持有的任何对象.wait()都会使线程陷入阻塞吗...
- 如何运用模板之家做html,Flask框架如何使用HTML模板
- 解决高并发(数据库锁机制)
- C# “配置系统未能初始化”
- 代码:灰度重心法求激光中心线(matlab)
- AR/VR研究框架——迎接AR元年
- 第十周项目2——二叉树的便利的递归算法
- Python 替换同义词(jieba 实现)
- 猜数字游戏c语言提示范围,【游戏编程】猜数字游戏(C语言)
- 深圳基因组所刘永鑫组招聘博士后4名(3年100万+文章奖励+项目绩效)
- 友盟推送点击行为处理
- 玩转 Jasper Report(1) Jaspersoft Studio 安装使用教程
- Code for city 黑客松大赛——万元大奖等你来拿
热门文章
- Git笔记(7) 撤消操作
- ROS笔记(7) 话题通信
- carlife android 无线,carlife无线连接流程是什么
- 外贸网站香港服务器,香港云服务器更适合小型外贸网站使用
- linux7改网卡名eth,CentOS7修改网卡名ensXX称为eth0
- springcloud灰度发布实现方案
- Java多线程学习(二)synchronized关键字(1)
- ubuntu14.04 在自带python2.7上安装python3.3.5 可以用但是有问题
- 软件工程之中期学习总结
- CALayer3-层的属性