Python 序列化和反序列化库 MarshMallow 的用法实例代码
序列化(Serialization)与反序列化(Deserialization)是RESTfulAPI开发中绕不开的一环,开发时,序列化与反序列化的功能实现中通常也会包含数据校验(Validation)相关的业务逻辑。
Marshmallow是一个强大的轮子,很好的实现了object->dict,objects->list,string->dict和string->list。
MarshmallowisanORM/ODM/framework-agnosticlibraryforconvertingcomplexdatatypes,suchasobjects,toandfromnativePythondatatypes.
--Frommarshmallow官方文档
Marshmallow的使用,将从下面几个方面展开,在开始之前,首先需要一个用于序列化和反序列化的类,我直接与marshmallow官方文档保持一致了:
classUser(object): def__init__(self,name,email): self.name=name self.email=email self.created_at=dt.datetime.now()
在很多情况下,我们会有把Python对象进行序列化或反序列化的需求,比如开发RESTAPI,比如一些面向对象化的数据加载和保存,都会应用到这个功能。
比如这里看一个最基本的例子,这里给到一个User的Class定义,再给到一个data数据,像这样:
classUser(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这两个库,它们二者的组合可以非常方便地实现对象的序列化和反序列化。
譬如这样:
fromattrimportattrs,attrib fromcattrimportstructure,unstructure @attrs classUser(object): name=attrib() age=attrib() data={ 'name':'Germey', 'age':23 } user=structure(data,User) print('user',user) json=unstructure(user) print('json',json)
运行结果:
userUser(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这个库,需要先安装下:
pip3installmarshmallow
好了之后,我们在之前的基础上定义一个Schema,如下:
classUserSchema(Schema): name=fields.Str() age=fields.Integer() @post_load defmake(self,data,**kwargs): returnUser(**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到UserList的转换。
有人说,如果是单个数据怎么办呢,只需要把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<->dictobjects<->list
验证
当然,上面的功能其实并不足以让你觉得marshmallow有多么了不起,其实就是一个对象到基本数据的转换嘛。但肯定不止这些,marshmallow还提供了更加强大啊功能,比如说验证,Validation。
比如这里我们将age这个字段设置为hello,它无法被转换成数值类型,所以肯定会报错,样例如下:
data={ 'name':'Germey', 'age':'hello' } frommarshmallowimportValidationError try: schema=UserSchema() user,errors=schema.load(data) print(user,errors) exceptValidationErrorase: print('e.message',e.messages) print('e.valid_data',e.valid_data)
这里如果加载报错,我们可以直接拿到Error的messages和valid_data对象,它包含了错误的信息和正确的字段结果,运行结果如下:
e.message{'age':['Notavalidinteger.']}e.valid_data{'name':'Germey'}
因此,比如我们想要开发一个功能,比如用户注册,表单信息就是提交过来的data,我们只需要过一遍Validation,就可以轻松得知哪些数据符合要求,哪些不符合要求,接着再进一步进行处理。
当然验证功能肯定不止这一些,我们再来感受一下另一个示例:
frompprintimportpprint frommarshmallowimportSchema,fields,validate,ValidationError classUserSchema(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) exceptValidationErroraserr: pprint(err.messages)
比如这里的validate字段,我们分别校验了name、permission、age三个字段,校验方式各不相同。
如name我们要判断其最小值为1,则使用了Length对象。permission必须要是几个字符串之一,这里又使用了OneOf对象,age又必须是介于某个范围之间,这里就使用了Range对象。
下面我们故意传入一些错误的数据,看下运行结果:
{'age':['Mustbegreaterthanorequalto18andlessthanorequalto40.'],'name':['Shorterthanminimumlength1.'],'permission':['Mustbeoneof:read,write,admin.']}
可以看到,这里也返回了数据验证的结果,对于不符合条件的字段,一一进行说明。
另外我们也可以自定义验证方法:
frommarshmallowimportSchema,fields,ValidationError defvalidate_quantity(n): ifn<0: raiseValidationError('Quantitymustbegreaterthan0.') ifn>30: raiseValidationError('Quantitymustnotbegreaterthan30.') classItemSchema(Schema): quantity=fields.Integer(validate=validate_quantity) in_data={'quantity':31} try: result=ItemSchema().load(in_data) exceptValidationErroraserr: print(err.messages)
通过自定义方法,同样可以实现更灵活的验证,运行结果:
{'quantity':['Quantitymustnotbegreaterthan30.']}
对于上面的例子,还有更优雅的写法:
frommarshmallowimportfields,Schema,validates,ValidationError classItemSchema(Schema): quantity=fields.Integer() @validates('quantity') defvalidate_quantity(self,value): ifvalue<0: raiseValidationError('Quantitymustbegreaterthan0.') ifvalue>30: raiseValidationError('Quantitymustnotbegreaterthan30.')
通过定义方法并用validates修饰符,使得代码的书写更加简洁。
必填字段
如果要想定义必填字段,只需要在fields里面加入required参数并设置为True即可,另外我们还可以自定义错误信息,使用error_messages即可,例如:
frompprintimportpprint frommarshmallowimportSchema,fields,ValidationError classUserSchema(Schema): name=fields.String(required=True) age=fields.Integer(required=True,error_messages={'required':'Ageisrequired.'}) city=fields.String( required=True, error_messages={'required':{'message':'Cityrequired','code':400}}, ) email=fields.Email() try: result=UserSchema().load({'email':'foo@bar.com'}) exceptValidationErroraserr: pprint(err.messages)
默认字段
对于序列化和反序列化字段,marshmallow还提供了默认值,而且区分得非常清楚!如missing则是在反序列化时自动填充的数据,default则是在序列化时自动填充的数据。
例如:
frommarshmallowimportSchema,fields importdatetimeasdt importuuid classUserSchema(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属性名,当然也可以自定义,如:
classUserSchema(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'}
反序列化也是一样,例如:
classUserSchema(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非常好用的地方,例如:
fromdatetimeimportdate frommarshmallowimportSchema,fields,pprint classArtistSchema(Schema): name=fields.Str() classAlbumSchema(Schema): title=fields.Str() release_date=fields.Date() artist=fields.Nested(ArtistSchema()) bowie=dict(name='DavidBowie') album=dict(artist=bowie,title='HunkyDory',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内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。