Python 序列化和反序列化库 MarshMallow 的用法实例代码


Posted in Python onFebruary 25, 2020

序列化(Serialization)与反序列化(Deserialization)是RESTful API 开发中绕不开的一环,开发时,序列化与反序列化的功能实现中通常也会包含数据校验(Validation)相关的业务逻辑。

Marshmallow 是一个强大的轮子,很好的实现了 object -> dict , objects -> list, string -> dict和 string -> list。

Marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes.

-- From marshmallow官方文档

Marshmallow的使用,将从下面几个方面展开,在开始之前,首先需要一个用于序列化和反序列化的类,我直接与marshmallow官方文档保持一致了:

class User(object):
 def __init__(self, name, email):
  self.name = name
  self.email = email
  self.created_at = dt.datetime.now()

在很多情况下,我们会有把 Python 对象进行序列化或反序列化的需求,比如开发 REST API,比如一些面向对象化的数据加载和保存,都会应用到这个功能。

比如这里看一个最基本的例子,这里给到一个 User 的 Class 定义,再给到一个 data 数据,像这样:

class User(object):
 def __init__(self, name, age):
 self.name = name
 self.age = age
data = [{
 '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 的数据类型不同,如何针对不同的数据类型进行针对性的类型转换,这个你有更好的实现方案吗?

初步思路

之前我写过一篇文章介绍过 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_load
 def 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, ValidationError
class 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, ValidationError
def 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, ValidationError
class 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, ValidationError
class 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 uuid
class 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, pprint
class 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 序列化和反序列化库 MarshMallow 的用法实例代码的文章就介绍到这了,更多相关Python 序列化和反序列化库 MarshMallow 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python简单调用MySQL存储过程并获得返回值的方法
Jul 20 Python
Python 由字符串函数名得到对应的函数(实例讲解)
Aug 10 Python
Python使用add_subplot与subplot画子图操作示例
Jun 01 Python
Python输出\u编码将其转换成中文的实例
Dec 15 Python
python绘制漏斗图步骤详解
Mar 04 Python
详解pandas.DataFrame中删除包涵特定字符串所在的行
Apr 04 Python
python binascii 进制转换实例
Jun 12 Python
详解pandas删除缺失数据(pd.dropna()方法)
Jun 25 Python
Django 迁移、操作数据库的方法
Aug 02 Python
Python3.7+tkinter实现查询界面功能
Dec 24 Python
pytorch中获取模型input/output shape实例
Dec 30 Python
python将下载到本地m3u8视频合成MP4的代码详解
Nov 24 Python
python用pip install时安装失败的一系列问题及解决方法
Feb 24 #Python
python numpy--数组的组合和分割实例
Feb 24 #Python
python梯度下降算法的实现
Feb 24 #Python
利用python实现逐步回归
Feb 24 #Python
python数据分析:关键字提取方式
Feb 24 #Python
python数据预处理 :数据共线性处理详解
Feb 24 #Python
使用python实现多维数据降维操作
Feb 24 #Python
You might like
PHP输出时间差函数代码
2013/01/28 PHP
Symfony2创建页面实例详解
2016/03/18 PHP
php处理带有中文URL的方法
2016/07/11 PHP
PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】
2017/11/17 PHP
jQuery+css实现图片滚动效果(附源码)
2013/03/18 Javascript
jquery导航制件jquery鼠标经过变色效果示例
2013/12/05 Javascript
图片翻转效果具体实现代码
2014/01/09 Javascript
滚动条响应鼠标滑轮事件实现上下滚动的js代码
2014/06/30 Javascript
js中的getAttribute方法使用示例
2014/08/01 Javascript
JavaScript截取字符串的2个函数介绍
2014/08/27 Javascript
js实现右下角提示框的方法
2015/02/03 Javascript
AngularJs根据访问的页面动态加载Controller的解决方案
2015/02/04 Javascript
js实现同一页面多个不同运动效果的方法
2015/04/10 Javascript
jQuery实现图像旋转动画效果
2016/05/29 Javascript
基于daterangepicker日历插件使用参数注意的问题
2017/08/10 Javascript
通俗解释JavaScript正则表达式快速记忆
2017/08/23 Javascript
JavaScript引用类型Function实例详解
2018/08/09 Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
2020/07/24 Javascript
vue下拉刷新组件的开发及slot的使用详解
2020/12/23 Vue.js
[01:11:21]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第三场 3月7日
2021/03/11 DOTA
Python中关于使用模块的基础知识
2015/05/24 Python
Python使用三种方法实现PCA算法
2017/12/12 Python
Python实现连接两个无规则列表后删除重复元素并升序排序的方法
2018/02/05 Python
python3中函数参数的四种简单用法
2018/07/09 Python
python实现高斯判别分析算法的例子
2019/12/09 Python
django前端页面下拉选择框默认值设置方式
2020/08/09 Python
加拿大租车网站:Enterprise Rent-A-Car
2018/07/26 全球购物
香港最大的洋酒零售连锁店:屈臣氏酒窖(Watson’s Wine)
2018/12/10 全球购物
艺术系大学生毕业个人自我评价
2013/09/19 职场文书
国际贸易专业推荐信
2013/11/15 职场文书
战友聚会策划方案
2014/06/13 职场文书
计划生育工作总结2015
2015/04/03 职场文书
四则混合运算教学反思
2016/02/23 职场文书
2016最新离婚协议书范本及程序
2016/03/18 职场文书
演讲稿之我的初心我的成长
2019/08/12 职场文书
java调用Restful接口的三种方法
2021/08/23 Java/Android