Marshmallow详解

注意:这里的marshmallow版本是预发行版本3.x,非而不是正式版本2.x。版本3与版本2有一些差别,望周知。

文档说明:https://marshmallow.readthedocs.io

marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的库,简而言之,就是实现object -> dict, objects -> list, string -> dict 和 string -> list。

序列化:序列化的意思是将数据对象转化为可存储或可传输的数据类型

反序列化:将可存储或可传输的数据类型转化为数据对象

要进行序列化或反序列化,首先我们需要一个用来操作的object,这里我们先定义一个类:

import datetime as dt

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


1. Scheme

要对一个类或者一个json数据实现相互转换(即序列化和反序列化), 需要一个中间载体, 这个载体就是Schema,另外Schema还可以用来做数据验证。

# 这是一个简单的Scheme
from marshmallow import Schema, fields class UserSchema(Schema):
name = fields.String()
email = fields.Email()
created_time = fields.DateTime()

2. Serializing(序列化)

使用scheme的dump()方法来序列化对象,返回的是dict格式的数据

另外schema的dumps()方法序列化对象,返回的是json编码格式的字符串。


user = User(name="TTY", email="tty@python.org")
schema = UserSchema()
res = schema.dump(user)
print(res)
# {'email': 'tty@python.org', 'name': 'TTY', 'created_time': '2019-08-05T14:43:51.168241+00:00'} res2 = schema.dumps(user)
print(res2)
# '{"name": "TTY", "created_time": "2019-08-05T14:46:07.111755+00:00", "email": "tty@python.org"}'

3. 过滤输出

当不需要输出所有的字段时,可以在实例化Scheme时,声明only参数,来指定输出:

summary_schema = UserSchema(only=("name", "email"))
res = summary_schema.dump(user)
print(res)
{'name': 'TTY', 'email': 'tty@python.org'}

only参数用来指定输出的字段,也可以用exclude参数来排除不输出的字段,达到一样的效果。

4. Deserializing(反序列化)

schema的load()方法与dump()方法相反,用于dict类型的反序列化。他将输入的字典格式数据转换成应用层数据结构。他也能起到验证输入的字典格式数据的作用。

同样,也有对json解码的loads()方法。用于string类型的反序列化。

默认情况下,load()方法返回一个字典,当输入的数据的值不匹配字段类型时,抛出 ValidationError 异常。

schema = UserSchema()
res = schema.load(user_data)
print(res)
# {'email': 'tty2@python.org', 'created_time': datetime.datetime(2019, 8, 5, 14, 46, 7), 'name': 'tty2'}

对反序列化而言, 将传入的dict变成object更加有意义. 在Marshmallow中, dict -> object的方法需要自己实现, 然后在该方法前面加上一个装饰器post_load即可

class UserSchema(Schema):
name = fields.String()
email = fields.Email()
created_time = fields.DateTime() @post_load
def make_user(self, data):
return User(**data)

这样每次调用load()方法时, 会按照make_user的逻辑, 返回一个User类对象。

user_data = {
"name": "tty2",
"email": "tty2@python.org"
} schema = UserSchema()
res = schema.load(user_data)
print(res)
# <__main__.User object at 0x0000027BE9678128>
user = res
print("name: {} email: {}".format(user.name, user.email))
# name: tty2 email: tty2@python.org

5. 处理多个对象的集合

多个对象的集合如果是可迭代的,那么也可以直接对这个集合进行序列化或者反序列化。在实例化Scheme类时设置参数many=True

也可以不在实例化类的时候设置,而在调用dump()方法的时候传入这个参数。

user1 = User(name="tty1", email="tty1@python.org")
user2 = User(name="tty2", email="tty2@python.org")
users = [user1, user2] # 第一种方法
schema = UserSchema(many=True)
res = schema.dump(users) # 第二种方法
# schema = UserSchema()
# res = schema.dump(users, many=True) print(res)
# [{'created_time': '2019-08-05T15:09:19.781325+00:00', 'email': 'tty1@python.org', 'name': 'tty1'},
# {'created_time': '2019-08-05T15:09:19.781325+00:00', 'email': 'tty2@python.org', 'name': 'tty2'}]

6. Validation(验证)

当不合法的数据通过Schema.load()或者Schema.loads()时,会抛出一个 ValidationError 异常。ValidationError.messages属性有验证错误信息,验证通过的数据在 ValidationError.valid_data 属性中

我们捕获这个异常,然后做异常处理。首先需要导入ValidationError这个异常

from marshmallow import Schema, fields, ValidationError

class UserSchema(Schema):
name = fields.String()
email = fields.Email()
created_time = fields.DateTime() try:
res = UserSchema().load({"name": "ttty", "email": "ttty"})
except ValidationError as e:
print("错误信息:{} 合法数据:{}".format(e.messages, e.valid_data))
# 错误信息:{'email': ['Not a valid email address.']} 合法数据:{'name': 'ttty'}
``
当验证一个数据集合的时候,返回的错误信息会以 错误序号-错误信息 的键值对形式保存在errors中 ```python
user_data = [
{'email': 'mick@stones.com', 'name': 'Mick'},
{'email': 'invalid', 'name': 'Invalid'},
{'name': 'Keith'},
{'email': 'charlie@stones.com'},
]
try:
schema = UserSchema(many=True)
res = schema.load(user_data)
except ValidationError as e:
print("错误信息:{} 合法数据:{}".format(e.messages, e.valid_data)) # 错误信息:{1: {'email': ['Not a valid email address.']}}
# 合法数据:[{'email': 'mick@stones.com', 'name': 'Mick'},
# {'name': 'Invalid'},
# {'name': 'Keith'},
# {'email': 'charlie@stones.com'}]

可以看到上面,有错误信息,但是对于没有传入的属性则没有检查,也就是说没有规定属性必须传入。

在Schema里规定不可缺省字段:设置参数required=True

class UserSchema(Schema):
name = fields.String(required=True)
email = fields.Email()
created_time = fields.DateTime()

再次进行验证:

try:
schema = UserSchema(many=True)
res = schema.load(user_data)
except ValidationError as e:
print("错误信息:{} 合法数据:{}".format(e.messages, e.valid_data)) # 错误信息:{1: {'email': ['Not a valid email address.']},
# 3: {'name': ['Missing data for required field.']}}
# 合法数据:[{'email': 'mick@stones.com', 'name': 'Mick'},
# {'name': 'Invalid'},
# {'name': 'Keith'},
# {'email': 'charlie@stones.com'}]

6.1 自定义验证信息

在编写Schema类的时候,可以向内建的fields中设置validate参数的值来定制验证的逻辑, validate的值可以是函数, 匿名函数lambda, 或者是定义了__call__的对象。

class UserSchema(Schema):
name = fields.String(required=True, validate=lambda s: len(s)<6)
email = fields.Email()
created_time = fields.DateTime() user_data = {'name': 'InvalidName', 'email': 'tty@python.org'}
try:
res = UserSchema().load(user_data)
except ValidationError as e:
print(e.messages)
# {'name': ['Invalid value.']}

在验证函数中自定义异常信息:

from marshmallow import Schema, fields, ValidationError

def validate_name(name):
if len(name) <= 2:
raise ValidationError("name长度必须大于2位")
if len(name) >= 6:
raise ValidationError("name长度不能大于6位") class UserSchema(Schema):
name = fields.String(required=True, validate=validate_name)
email = fields.Email()
created_time = fields.DateTime() user_data = {'name': 'InvalidName', 'email': 'tty@python.org'}
try:
res = UserSchema().load(user_data)
except ValidationError as e:
print(e.messages)
# {'name': ['name长度不能大于6位']}

注意:只会在反序列化的时候发生验证!序列化的时候不会验证!

6.2 将验证函数写在Schema中变成验证方法

在Schema中,使用validates装饰器就可以注册验证方法。

from marshmallow import Schema, fields, ValidationError, validates

class UserSchema(Schema):
name = fields.String(required=True)
email = fields.Email()
created_time = fields.DateTime() @validates("name")
def validate_name(self, value):
if len(value) <= 2:
raise ValidationError("name长度必须大于2位")
if len(value) >= 6:
raise ValidationError("name长度不能大于6位") user_data = {'name': 'InvalidName', 'email': 'tty@python.org'}
try:
res = UserSchema().load(user_data)
except ValidationError as e:
print(e.messages)
# {'name': ['name长度不能大于6位']}

6.3 Required Fields(必填选项)

上面已经简单使用过required参数了。这里再简单介绍一下。

自定义required异常信息:

首先我们可以自定义在requird=True时缺失字段时抛出的异常信息:设置参数error_messages的值

class UserSchema(Schema):
name = fields.String(required=True, error_messages={"required": "name字段必须填写"})
email = fields.Email()
created_time = fields.DateTime() user = {"email": "tty@python.org"}
schema = UserSchema()
try:
res = schema.load(user)
except ValidationError as e:
print(e.messages)
# {'name': ['name字段必须填写']}

忽略部分字段:

使用required之后我们还是可以在传入数据的时候忽略这个必填字段。

class UserSchema(Schema):
name = fields.String(required=True)
age = fields.Integer(required=True) # 方法一:在load()方法设置partial参数的值(元组),表时忽略那些字段。
schema = UserSchema()
res = schema.load({"age": 42}, partial=("name",))
print(res)
# {'age': 42} # 方法二:直接设置partial=True
schema = UserSchema()
res = schema.load({"age": 42}, partial=True)
print(res)
# {'age': 42}

看起来两种方法是一样的,但是方法一和方法二有区别:方法一只忽略传入partial的字段,方法二会忽略除前面传入的数据里已有的字段之外的所有字段

6.4 对未知字段的处理

默认情况下,如果传入了未知的字段(Schema里没有的字段),执行load()方法会抛出一个 ValidationError 异常。这种行为可以通过更改 unknown 选项来修改。

unknown 有三个值:

  • EXCLUDE: exclude unknown fields(直接扔掉未知字段)
  • INCLUDE: accept and include the unknown fields(接受未知字段)
  • RAISE: raise a ValidationError if there are any unknown fields(抛出异常)

我们可以看到,默认的行为就是RAISE。有两种方法去更改:

方法一:在编写Schema类的时候在class Meta里修改

# 首先导入 EXCLUDE
from marshmallow import EXCLUDE class UserSchema(Schema):
name = fields.String(required=True, error_messages={"required": "name字段必须填写"})
email = fields.Email()
created_time = fields.DateTime() class Meta:
unknown = EXCLUDE

方法二:在实例化Schema类的时候设置参数unknown的值

class UserSchema(Schema):
name = fields.Str(required=True, error_messages={"required": "name字段必须填写"})
email = fields.Email()
created_time = fields.DateTime() shema = UserSchema(unknown=EXCLUDE)

7. Schema.validate(校验数据)

如果只是想用Schema去验证数据, 而不进行反序列化生成对象, 可以使用Schema.validate()

可以看到, 通过schema.validate()会自动对数据进行校验, 如果有错误, 则会返回错误信息的dict,没有错误则返回空的dict,通过返回的数据, 我们就可以确认验证是否通过.

class UserSchema(Schema):
name = fields.Str(required=True, error_messages={"required": "name字段必须填写"})
email = fields.Email()
created_time = fields.DateTime() user = {"name": "tty", "email": "tty@python"}
schema = UserSchema()
res = schema.validate(user)
print(res)
# {'email': ['Not a valid email address.']} user1 = {"name": "tty", "email": "tty@python.org"}
schema = UserSchema()
res1 = schema.validate(user1)
print(res1)
# {}

8. Specifying Serialization/Deserialization Keys(指定序列化/反序列化键)

8.1 Specifying Attribute Names(序列化时指定object属性对应fields字段)

Schema默认会序列化传入对象和自身定义的fields相同的属性, 然而你也会有需求使用不同的fields和属性名. 在这种情况下, 你需要明确定义这个fields将从什么属性名取值

import datetime as dt
from marshmallow import Schema, fields class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.created_time = dt.datetime.now() class UserSchema(Schema):
full_name = fields.String(attribute="name")
email_address = fields.Email(attribute="email")
created_at = fields.DateTime(attribute="created_time") user = User("ttty", email="ttty@python.org")
schema = UserSchema()
res = schema.dump(user)
print(res)

如上所示:UserSchema中的full_name,email_address,created_at分别从User对象的name,email,created_time属性取值。

8.2 反序列化时指定fields字段对应object属性

这个与上面相反,Schema默认反序列化传入字典和输出字典中相同的字段名. 如果你觉得数据不匹配你的schema, 可以传入load_from参数指定需要增加load的字段名(原字段名也能load, 且优先load原字段名)

class UserSchema(Schema):
full_name = fields.String(load_from="name")
email_address = fields.Email(load_from="email")
created_at = fields.DateTime(load_from="created_time") user = {"full_name": "ttty", "email_address": "ttty@python.org"}
schema = UserSchema()
res = schema.load(user)
print(res)
# {'email_address': 'ttty@python.org', 'full_name': 'ttty'}

8.3 让key同时满足序列化与反序列化的方法

class UserSchema(Schema):
full_name = fields.String(data_key="name")
email_address = fields.Email(data_key="email")
created_at = fields.DateTime(data_key="created_time") # 序列化
user = {"full_name": "ttty", "email_address": "ttty@python.org"}
schema = UserSchema()
res = schema.dump(user)
print(res)
# {'name': 'ttty', 'email': 'ttty@python.org'} # 反序列化
user1 = {"name": "ttty", "email": "ttty@python.org"}
schema = UserSchema()
res = schema.load(user1)
print(res)
# {'email_address': 'ttty@python.org', 'full_name': 'ttty'}

9. 重构:创建隐式字段

当Schema具有许多属性时,为每个属性指定字段类型可能会重复,特别是当许多属性已经是本地python的数据类型时。class Meta允许指定要序列化的属性,marshmallow将根据属性的类型选择适当的字段类型。

# 重构Schema
class UserSchema(Schema):
uppername = fields.Function(lambda obj: obj.name.upper()) class Meta:
fields = ("name", "email", "created_at", "uppername")

以上代码中, name将自动被格式化为String类型,created_at将被格式化为DateTime类型。

如果您希望指定除了显式声明的字段之外还包括哪些字段名,则可以使用附加选项。如下:

class UserSchema(Schema):
uppername = fields.Function(lambda obj: obj.name.upper()) class Meta:
# No need to include 'uppername'
additional = ("name", "email", "created_at")

10. 排序

对于某些用例,维护序列化输出的字段顺序可能很有用。要启用排序,请将ordered选项设置为true。这将指示marshmallow将数据序列化到collections.OrderedDict

from collections import OrderedDict

class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.created_time = dt.datetime.now() class UserSchema(Schema):
uppername = fields.Function(lambda obj: obj.name.upper()) class Meta:
fields = ("name", "email", "created_time", "uppername")
ordered = True u = User("Charlie", "charlie@stones.com")
schema = UserSchema()
res = schema.dump(u)
print(isinstance(res, OrderedDict))
# True
print(res)
# OrderedDict([('name', 'Charlie'), ('email', 'charlie@stones.com'), ('created_time', '2019-08-05T20:22:05.788540+00:00'), ('uppername', 'CHARLIE')])

11. “只读”与“只写”字段

在Web API的上下文中,序列化参数dump_only和反序列化参数load_only在概念上分别等同于只读和只写字段。

class UserSchema(Schema):
name = fields.Str()
# password is "write-only"
password = fields.Str(load_only=True)
# created_at is "read-only"
created_at = fields.DateTime(dump_only=True)

load时,dump_only字段被视为未知字段。如果unknown选项设置为include,则与这些字段对应的键的值将因此loaded而不进行验证。

12. 序列化/反序列化时指定字段的默认值

序列化时输入值缺失用default指定默认值。反序列化时输入值缺失用missing指定默认值。

class UserSchema(Schema):
id = fields.UUID(missing=uuid.uuid1)
birthdate = fields.DateTime(default=dt.datetime(2020, 9, 9)) # 序列化
res1 = UserSchema().dump({})
print(res1)
# {'birthdate': '2020-09-09T00:00:00+00:00'} # 反序列化
res = UserSchema().load({})
print(res)
# {'id': UUID('18f1eb3a-b7ec-11e9-82fb-8cec4b76ee65')}

13. 后续扩展

一个自定义字段的小例子:

from marshmallow import Schema, fields

class String128(fields.String):
"""
长度为128的字符串类型
""" default_error_messages = {
"type": "该字段只能是字符串类型",
"invalid": "该字符串长度必须大于6",
} def _deserialize(self, value, attr, data, **kwargs):
if not isinstance(value, str):
self.fail("type")
if len(value) < 6:
self.fail("invalid") class AppSchema(Schema):
name = String128(required=True)
priority = fields.Integer()
obj_type = String128()
link = String128()
deploy = fields.Dict()
description = fields.String()
projects = fields.List(cls_or_instance=fields.Dict) app = {
"name": "app11",
"priority": 2,
"obj_type": "web",
"link": "123.123.00.2",
"deploy": {"deploy1": "deploy1", "deploy2": "deploy2"},
"description": "app111 test111",
"projects": [{"id": 2}]
} schema = AppSchema()
res = schema.validate(app)
print(res)
# {'obj_type': ['该字符串长度必须大于6'], 'name': ['该字符串长度必须大于6']}

Marshmallow详解的更多相关文章

  1. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  2. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  3. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  4. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  5. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  6. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  7. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

  8. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  9. Node.js npm 详解

    一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...

随机推荐

  1. 海盗分金问题SQL求解(贪心算法)

    问题 经济学上有个"海盗分金"模型:是说5个海盗抢得100枚金币,他们按抽签的顺序依次提方案:首先由1号提出分配方案,然后5人表决,超过半数同意方案才被通过,否则他将被扔入大海喂鲨 ...

  2. ETL DAG调度策略

    1.目前etl的fetch task策略是基于任务子孙任务数和任务优先级获得task list 2.然后遍历task list 查看任务是否具备执行条件 集群资源校验(yarn/hdfs)<如果 ...

  3. Zabbix监控服务器磁盘I/O

    一.场景说明: 需要使用Zabbix监控服务器上各个磁盘的I/O使用率,当zabbix自身带的item无法满足我们的时候,则需自定义item.     包括: 磁盘读的次数 磁盘读的毫秒数 磁盘写的次 ...

  4. java 深copy

    public static<T> T deepClone(T src) throws IOException, ClassNotFoundException { Object obj = ...

  5. jquery-ui提供的拖拽方法

    项目当中遇到了任意拖动div标签的功能,找到了jqueryui提供的draggable的插件,这个插件可以实现任意的div的移动,也可以移动到整个屏幕或者在父元素的范围内进行移动. 插件的api    ...

  6. node-gyp 在此解决方案中一次生成一个项目。若要启用并行生成,请添加“/m”开关。

    在此解决方案中一次生成一个项目.若要启用并行生成,请添加“/m”开关. MSBUILD : error MSB3428: 未能加载 Visual C++ 组件“VCBuild.exe”.要解决此问题, ...

  7. wordpress中文目录出现“有点尴尬诶!该页无法显示"

    原因不详,可能是.htaccess.网上说删除后再更新固定链接会再生成,但是我没有.我又把原来的.htaccess上传后更改固定链接为“数字型”,测试后可以正常浏览. 然后又再更改为原来的“日期和名称 ...

  8. img border

  9. 20180610模拟赛T3——书本整理

    [问题描述] 小明的书架上放了许多书,为了使书架变得整洁,小明决定整理书架,他将所有书按高度大小排列,这样排了之后虽然整齐了许多,但小明发现,书本的宽度不同,导致书架看上去还是有些凌乱.小明把这个凌乱 ...

  10. 循环递减算法 [a,b,c] 求 ab,ac,bc

    有数组 lineList=[a,b,c] 求所有不同的两两组合 ,结果:ab,ac,bc lineList.forEach((lineA,lineIndex)=>{ ==len){ return ...