marshmallow 库的简单学习
marshmallow 是一个简单序列化/反序列化模块。 它可以很轻松的做到
object-->dict
objects-->list
等序列化操作,同时经过添加简单的函数做到
dict-->object
list-->objects
等反序列化操作。
同时,它对于数据的校检非常友好,有多种校检方式,同时相对于 Schema 库,它可以更有针对性的校检数据,序列化数据等(例如只取某些数据,只校检某些数据等)。
一、简单说明
marshmallow 库非常易于使用,下面将以实际的代码演示如何使用这个库。
先明确下使用流程。
创建类–>创建 Schema 类–>(创建 ValidSchema 类) 序列化<—>反序列化
二、创建Schema类
创建基础的类
# -*- coding:utf-8 -*-
from datetime import date
from marshmallow import Schema, fields, pprint, post_load
from marshmallow import ValidationError, validate, validates
class Artist(object):
"""基础艺术家类"""
def __init__(self, name):
self.name = name
class Album(object):
"""基础专辑类"""
def __init__(self, title, release_date, artist):
self.title = title
self.release_date = release_date
self.artist = artist
根据自己的类创建对应的 Schema 类,继承自 marshmallow 的 Schema 类。
class ArtistSchema(Schema):
"""艺术家的schema类"""
name = fields.Str()
class AlbumSchema(Schema):
"""专辑的schema类"""
title = fields.Str()
release_date = fields.Date()
artist = fields.Nested(ArtistSchema())
可以观察到,Schema 类的字段和基础类的成员命名一致,在这里先保持一致,后面会对字段进一步说明;同时可以观察到 Schema 里对数据类型进行了限定,例如 title 只能是字符串,release_date 只能是日期格式,这算是数据校检的第一步类型校检。
三、序列化和反序列化
1、定义
我之前一直不能理解什么是序列化,什么是反序列化,它到底有什么用。
维基定义是【序列化(serialization)在计算机科学的资料处理中,是指将数据结构或物件状态转换成可取用格式(例如存成档案,存于缓冲,或经由网络中传送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始物件相同语义的副本。】
通俗的说法就是将一个数据处理成可以存储或者传输的形式,这个过程叫序列化,反过来,将某种形式的数据处理成可以使用的数据就是反序列化。
举个 python 上的例子。python 有个内建 dict 类型,也就是字典类型,可以方便的处理键值对数据,这个数据暂存于内存中,如何将它保存或者将它传输,最简单的方法就是 json.dump(),将数据保存在 json 文件里,这个过程就称之为序列化。反过来,你拿到了一个 json 文件,通过 json.load(),将 json 文件里的数据转成了 dict 就可以直接使用,这个过程就称之为反序列化。
2、序列化
marshmallow 可以对一个 object 进行序列化成一个 dict,同时可以对一群 object 进行序列化成一个 list。
单个 object 的序列化:
下面是序列化的操作,可以看到进行了两次序列化,一次输出成 dict 类型,一次输出成 str 类型。
# 单个object,即object-->dict
bowie = Artist(name='David Bowie')
album = Album(artist=bowie, title='Hunky Dory', release_date=date(1971, 12, 17))
schema = AlbumSchema()
# 单个object的序列化
# schema.dump(obj) == obj-->dict
# schema.dumps(obj) == obj-->str
result_dict = schema.dump(album)
result_str = schema.dumps(album)
pprint(result_dict, indent=2)
pprint(type(result_dict))
pprint(result_str, indent=2)
pprint(type(result_str))
这是输出的结果。
{ u'artist': { u'name': u'David Bowie'},
u'release_date': '1971-12-17',
u'title': u'Hunky Dory'}
<type 'dict'>
'{"release_date": "1971-12-17", "title": "Hunky Dory", "artist": {"name": "David Bowie"}}'
<type 'str'>
序列化之后我们就可以进行保存或者传输,dict 存储为 json,str 存储为文本。
多个object的序列化: 下面是序列化的操作,可以看到进行了两次序列化,一次输出成 list 类型,其中 list 的每一个元素都是 dict 类型,一次输出成 str 类型。
```python
# 多个object,即objects-->list
alice = Artist(name='Alice')
bob = Artist(name='Bob')
cinderella = Artist(name='Cinderella')
alice_album = Album(title='To Alice', release_date=date(1971, 01, 01), artist=alice)
bob_album = Album(title='Kill Bob', release_date=date(1972, 02, 02), artist=bob)
cinderella_album = Album(title='Cinderella', release_date=date(1973, 03, 03), artist=cinderella)
album = [alice_album, bob_album, cinderella_album]
## Schema类字段many=True
schema = AlbumSchema(many=True)
# 多个object的序列化
# schema.dump(obj_list) == obj_list-->dict_list
# schema.dumps(obj_list) == obj_list-->str
result_list = schema.dump(album)
result_str = schema.dumps(album)
pprint(result_list, indent=2)
pprint(result_str, indent=2)
这是输出的结果。
[ { u'artist': { u'name': u'Alice'},
u'release_date': '1971-01-01',
u'title': u'To Alice'},
{ u'artist': { u'name': u'Bob'},
u'release_date': '1972-02-02',
u'title': u'Kill Bob'},
{ u'artist': { u'name': u'Cinderella'},
u'release_date': '1973-03-03',
u'title': u'Cinderella'}]
'[{"release_date": "1971-01-01", "title": "To Alice", "artist": {"name": "Alice"}}, {"release_date": "1972-02-02", "title": "Kill Bob", "artist": {"name": "Bob"}}, {"release_date": "1973-03-03", "title": "Cinderella", "artist": {"name": "Cinderella"}}]'
序列化之后我们就可以进行保存或者传输,list 不能直接存储,但是可以作为 dict 的一个 value 存储为 json,str 存储为文本。
3、反序列化
marshmallow 可以对一个 dict 或 str 进行反序列化成一个 object
,同时可以对一个 list 或 str 进行反序列化成一个 list,此时 list 的每个元素是 object
。
注意:marshmallow 默认并不能反序列化成 object
,但添加一个简单的方法就可以实现。
class AlbumSchema(Schema):
"""专辑的schema类"""
title = fields.Str()
release_date = fields.Date()
artist = fields.Nested(ArtistSchema())
@post_load()
def make_album(self, data):
"""添加了post_load()装饰器的方法,在反序列化时会执行这个逻辑"""
return Album(**data)
单个 object 的反序列化:
下面是反序列化的操作,可以看到进行了两次反序列化,一次将 dict 类型输出成 object
,一次将str类型输出 object
。
# 单个object的反序列化
# schema.load(dict) == dict-->dict
# schema.loads(str) == str-->dict
# if you want dict-->object, you should add a method in 'class AlbumSchema', then use the 'post_load()' decorator
# result_dict result_str是序列化后的结果
origin_dict = schema.load(result_dict)
origin_str = schema.loads(result_str)
pprint(origin_dict)
pprint(type(origin_dict))
pprint(origin_str)
pprint(type(origin_str))
这是输出的结果。
<__main__.Album object at 0x7f4784e48c10>
<class '__main__.Album'>
<__main__.Album object at 0x7f4784e48b90>
<class '__main__.Album'>
可以看出已经变成了对象。
多个 object 的反序列化:
# 多个object的反序列化
# schema.load(list) == list-->list
# schema.loads(str) == str-->list
# 在这里,反序列化后会生成三个对象的列表,即列表的每一个元素都是一个实例
origin_list = schema.load(result_list)
origin_str = schema.loads(result_str)
pprint(origin_list)
pprint(type(origin_list))
pprint(origin_str)
pprint(type(origin_str))
以下是结果,可以很清楚的看到反序列化为了一个 list,list 的每个元素是 object
。
[<__main__.Album object at 0x7f51eb7ebed0>,
<__main__.Album object at 0x7f51eb7ebf10>,
<__main__.Album object at 0x7f51eb7ebf50>]
<type 'list'>
[<__main__.Album object at 0x7f51eb7ebf90>,
<__main__.Album object at 0x7f51eb7ebfd0>,
<__main__.Album object at 0x7f51eb781050>]
<type 'list'>
4、部分序列化
之前的序列化都是对 object 完整的序列化操作,假设现在只需要传输或者保存 object 的某些字段该怎么做?Schema 类提供了一个 only 属性,只序列化 only 内的字段。
# 不加only
schema = AlbumSchema()
result = schema.dump(alice_album)
pprint(result)
# 添加only=['title', 'release_date']
schema = AlbumSchema(only=['title', 'release_date'])
result = schema.dump(alice_album)
pprint(result)
# 不加only输出结果
{u'release_date': '1971-01-01', u'title': u'To Alice', u'artist': {u'name': u'Alice'}}
# 添加only输出结果
{u'release_date': '1971-01-01', u'title': u'To Alice'}
我们需要哪些字段就将哪些字段加到 only 列表中,这样可以减少序列化后的数据量,传输也更轻松。
5、部分反序列化(这部分应放在特殊用法)
[官方文档(https://marshmallow.readthedocs.io/en/3.0/quickstart.html) 这部分的标题是 Partial Loading,意为部分加载,它指的是可以不对 Schema 类里的所有字段进行校检。
举个简单的例子,定义了一个有十个字段的类,又定义了一个相关的 Schema 类。假设我需要对传来的部分数据做校检该怎么做?直接放进 schema.load() 必定会出错,这时候就需要用到 partial 属性。 (但还是会有个前提,即 Schema 类中不实现生成对象的方法)。
四、数据校检
终于来到了数据校检部分。数据校检可以简单地分为数据类型检查和数据值的检查。marshmallow 的数据校检过程中甚至可以主动格式化传来的数据,例如使其大写或者小写等。
- fields 类型校检
- fields 的 required 属性校检
- fields 的 validate 属性校检
- fields 的 method 校检
可以看出,主要是围绕 fields 进行校检,在看校检过程之前先解释下 ValidationError 和 Field 概念。
ValidationError:marshmallow里的错误类型,通过try——except捕捉到这个错误后,可以直接输出message信息,它将以字典形式放回所有校检失败的原因
Field:各种类型数据的字段类
下面将直接通过代码说明校检过程。
class User(object):
"""用户类"""
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
class UserSchema(Schema):
"""user普通schema类"""
name = fields.Str()
age = fields.Number()
email = fields.Email()
class ValidUserSchema(UserSchema):
"""
user有效值判断schema
有多种判断方式
"""
# 先是判断name是不是Str类型
# fields添加了required=True属性,指的是name必须存在
name = fields.Str(required=True)
# 先是判断email是不是Email类型
# field添加了validate属性,validate可以用多种方式校检,这里用到的是正则匹配
# email必须正则匹配特定的规则
email = fields.Email(validate=validate.Regexp('[0-9a-zA-Z.]+@woqutech.com'))
# age可以通过设定validate进行值的范围判断
age = fields.Number(validate=lambda n: 18 <= n <= 41)
age = fields.Number(validate=validate.Range(18, 41))
# 下面是fields的method校检方式
age = fields.Number()
@validates('age')
def validate_age(self, value):
"""
age同样可以通过method进行值的判断,需要装饰器validates(key_name)
"""
if value < 0:
raise ValidationError('Age should not < 0')
if value > 100:
raise ValidationError('Age should not > 100')
user = {'name': 'Alice', 'age': -12, 'email': '123.4?5@woqutech.com'}
try:
result = ValidUserSchema().load(user)
except ValidationError as e:
print(e.message)
五、特殊用法
- Partial Loading
- Handling Unknown Fields
- Schema.validate
- Specifying Attribute Names
- Specifying Serialization/Deserialization Keys
- Refactoring: Implicit Field Creation
- Ordering Output
- “Read-only” and “Write-only” Fields
- Specify Default Serialization/Deserialization Values