python之Marshmallow

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

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

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

import datetime as dtclass User:def __init__(self, name, email):self.name = nameself.email = emailself.created_time = dt.datetime.now()

1、Scheme

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

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

2、Serializing(序列化)

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

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

user = User("lhh","2432783449@qq.com")
schema = UserSchema()
res = schema.dump(user)
print(res)
# {'email': '2432783449@qq.com', 'created_time': '2021-05-28 20:43:08.946112', 'name': 'lhh'}  dictres2 = schema.dumps(user)
print(res2)
# {"name": "lhh", "email": "2432783449@qq.com", "created_time": "2021-05-28 20:45:17.418739"}  json

3、过滤输出

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

summary_schema = UserSchema(only={"name","email"})
res = summary_schema.dump(user)
print(res)

4、Deserializing(反序列化)

schema的load()方法与dump()方法相反,用于dict类型的反序列化。他将输入的字典格式数据转换成应用层数据结构。他也能起到验证输入的字典格式数据的作用。
同样,也有对json解码的loads()方法。用于string类型的反序列化。
默认情况下,load()方法返回一个字典,当输入的数据的值不匹配字段类型时,抛出 ValidationError 异常。

user_data = {"name": "lhh","email": "2432783449@qq.com","created_time": "2021-05-28 20:45:17.418739"
}
schema = UserSchema()
res = schema.load(user_data)
print(res)
# {'created_time': '2021-05-28 20:45:17.418739', 'email': '2432783449@qq.com', 'name': 'lhh'}

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

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

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

user_data = {"name": "lhh","email": "2432783449@qq.com"
}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: lhh    email: 2432783449@qq.com

5、处理多个对象的集合

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

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

user1 = User(name="lhh1", email="2432783449@qq.com")
user2 = User(name="lhh2", email="2432783449@qq.com")
users = [user1, user2]# 第一种方法
schema = UserSchema(many=True)
res = schema.dump(users)
print(res)# 第二种方法
schema = UserSchema()
res = schema.dump(users,many=True)
print(res)

6、Validation(验证)

当不合法的数据通过Schema.load()或者Schema.loads()时,会抛出一个 ValidationError 异常。ValidationError.messages属性有验证错误信息,验证通过的数据在 ValidationError.valid_data 属性中
我们捕获这个异常,然后做异常处理。首先需要导入ValidationError这个异常

from marshmallow import Schema,fields,ValidationErrorclass UserSchema(Schema):name = fields.String()email = fields.Email()created_time = fields.DateTime()try:res = UserSchema().load({"name":"lhh","email":"lhh"})except ValidationError as e:print(f"错误信息:{e.messages}  合法数据:{e.valid_data}")'''当验证一个数据集合的时候,返回的错误信息会以 错误序号-错误信息 的键值对形式保存在errors中
'''
user_data = [{'email': '2432783449@qq.com', 'name': 'lhh'},{'email': 'invalid', 'name': 'Invalid'},{'name': 'wcy'},{'email': '2432783449@qq.com'},
]try:schema = UserSchema(many=True)res = schema.load(user_data)print(res)
except ValidationError as e:print("错误信息:{}   合法数据:{}".format(e.messages, e.valid_data))

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

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

可以看到上面,有错误信息,但是对于没有传入的属性则没有检查,也就是说没有规定属性必须传入。
在Schema里规定不可缺省字段:设置参数required=True

6.1 自定义验证信息

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

from marshmallow import Schema,fields,ValidationErrorclass 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":"2432783449@qq.com"}
try:res = UserSchema().load(user_data)
except ValidationError as e:print(e.messages)

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

#encoding=utf-8
from marshmallow import Schema,fields,ValidationErrordef 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":"2432783449@qq.com"}
try:res = UserSchema().load(user_data)
except ValidationError as e:print(e.messages)

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

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

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

#encoding=utf-8
from marshmallow import Schema, fields, ValidationError, validatesclass 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":"2432783449@qq.com"}
try:res = UserSchema().load(user_data)
except ValidationError as e:print(e.messages)

6.3 Required Fields(必填选项)

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

自定义required异常信息:

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

#encoding=utf-8
from marshmallow import Schema, fields, ValidationError, validatesclass UserSchema(Schema):name = fields.String(required=True, error_messages={"required":"name字段必须的"})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 = {"email":"2432783449@qq.com"}
try:res = UserSchema().load(user_data)
except ValidationError as e:print(e.messages)

忽略部分字段:

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

#encoding=utf-8
from marshmallow import Schema, fields, ValidationError, validatesclass 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里修改

from marshmallow import EXCLUDE,Schema,fieldsclass 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,通过返回的数据, 我们就可以确认验证是否通过.

#encoding=utf-8
from marshmallow import Schema,fields,ValidationErrorclass UserSchema(Schema):name = fields.Str(required=True, error_messages={"required": "name字段必须填写"})email = fields.Email()created_time = fields.DateTime()user = {"name":"lhh","email":"2432783449"}
schema = UserSchema()
res = schema.validate(user)
print(res)  # {'email': ['Not a valid email address.']}user = {"name":"lhh","email":"2432783449@qq.com"}
schema = UserSchema()
res = schema.validate(user)
print(res)  # {}

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

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

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

from marshmallow import fields,Schema,ValidationError
import datetime as dtclass User:def __init__(self, name, email):self.name = nameself.email = emailself.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("lhh",email="2432783449@qq.com")
schema = UserSchema()
res = schema.dump(user)
print(res)
# {'email_address': '2432783449@qq.com', 'full_name': 'lhh', 'created_at': '2021-05-29T09:24:38.186191'}

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

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

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

from marshmallow import fields,Schema,ValidationError
import datetime as dtclass 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":"lhh","email_address":"2432783449@qq.com"}
schema = UserSchema()
res = schema.load(user)
print(res)
# {'full_name': 'lhh', 'email_address': '2432783449@qq.com'}

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

#encoding=utf-8
from marshmallow import fields,ValidationError,Schemaclass 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": "lhh", "email_address": "2432783449@qq.com"}
schema = UserSchema()
res = schema.dump(user)
print(res)
# {'name': 'lhh', 'email': '2432783449@qq.com'}# 反序列化
user = {'name': 'lhh', 'email': '2432783449@qq.com'}
schema = UserSchema()
res = schema.load(user)
print(res)
# {'full_name': 'lhh', 'email_address': '2432783449@qq.com'}

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
import datetime as dt
from marshmallow import fields,ValidationError,Schemaclass User:def __init__(self, name, email):self.name = nameself.email = emailself.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 = Trueuser = User("lhh", "2432783449@qq.com")
schema = UserSchema()
res = schema.dump(user)
print(isinstance(res,OrderedDict))  # 判断变量类型
# True
print(res)
# OrderedDict([('name', 'lhh'), ('email', '2432783449@qq.com'), ('created_time', '2021-05-29T09:40:46.351382'), ('uppername', 'LHH')])

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

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

from marshmallow import Schema,fieldsclass UserSchema(Schema):name = fields.Str()password = fields.Str(load_only=True)  # 等于只写created_at = fields.DateTime(dump_only=True)  # 等于只读

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

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

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

#encoding=utf-8
import uuid
import datetime as dt
from marshmallow import fields,ValidationError,Schemaclass UserSchema(Schema):id = fields.UUID(missing=uuid.uuid1)birthday = fields.DateTime(default=dt.datetime(1996,11,17))# 序列化
res = UserSchema().dump({})
print(res)
# {'birthday': '1996-11-17T00:00:00'}# 反序列化
res = UserSchema().load({'birthday': '1996-11-17T00:00:00'})
print(res)
# {'id': UUID('751d95db-c020-11eb-83eb-001a7dda7115'), 'birthday': datetime.datetime(1996, 11, 17, 0, 0)}

13. 后续扩展

from marshmallow import Schema, fieldsclass 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']}

参考文章:https://www.cnblogs.com/xingxia/p/python_Marshmallow.html

Python之marshmallow相关推荐

  1. python,jsonschema, marshmallow, “None is not of type ‘string‘

    1.示例代码: class TestDto(Schema):swag_in = "body" NAME = fields.String(type=["string&quo ...

  2. python flask 项目实践

    架构:python+ flask +marshmallow+itsdangerous+pymysql 数据库:mysql 实现功能:登录.验证码.鉴权.用户.上传.下载.错误统一处理 api格式: r ...

  3. qos cbs_我在CBS Interactive担任视频软件工程师实习生的夏天

    qos cbs CBSi的生活| 视频处理团队 (Life at CBSi | Video Processing Team) I was extremely excited when I was ac ...

  4. [Python] Marshmallow QuickStart

    常用小操作索引 指定fields必须:fields.String(required=True) 部分loading: 不load name:result = UserSchema.load({'ema ...

  5. python那么多库怎么学_这个 Python 库有必要好好学学

    " 阅读本文大概需要 6 分钟. " 在很多情况下,我们会有把 Python 对象进行序列化或反序列化的需求,比如开发 REST API,比如一些面向对象化的数据加载和保存,都会应 ...

  6. Python 中更优雅的环境变量设置方案

    本文授权转载自公众号:进击的Coder 在运行一个项目的时候,我们经常会遇到设置不同环境的需求,如设置是开发环境.测试环境还是生产环境,或者在某些设置里面可能还需要设置一些变量开关,如设置调试开关.日 ...

  7. 值得收藏的45个Python优质资源(附链接)

    授权自AI科技大本营(ID:rgznai100) 本文共4157字,建议阅读7分钟. 本文为大家挑选了适合初学的45个Python的优质项目. 热门资源博客 Mybridge AI 比较了18000个 ...

  8. 值得收藏的45个Python优质资源

    REST API:使用 Python,Flask,Flask-RESTful 和 Flask-SQLAlchemy 构建专业的 REST API https://www.udemy.com/rest- ...

  9. 华为python有必要学吗_【华为云技术分享】这个 Python 库有必要好好学学

    这里看一个最基本的例子,这里给到一个 User 的 Class 定义,再给到一个 data 数据,像这样: 1 class User(object):2 def __init__(self, name ...

  10. 2013流行Python项目汇总

    2013流行Python项目汇总 转自:http://www.kankanews.com/ICkengine/archives/102963.shtml Python作为程序员的宠儿,越来越得到人们的 ...

最新文章

  1. 项目经理怎么运用思维导图
  2. vim编程 插入 保存不退出 保存退出 退出不保存 另存为其他文件名 保存覆盖现有文件...
  3. mysql text转varchar_关于企业面试中:“ Mysql重点 ”的28道面试题解析!
  4. Dr Robot 2015.6—7月
  5. 分享一下把pdftk的合并pdf功能添加到TC(Totalcommander)
  6. 一种可以解决python读取文件中文出乱码的方法
  7. LVM与软RAID整理笔记
  8. Laravel+DingoAPI+Passport使用邮箱或手机号或uid登录
  9. angular1的复选框指令--checklistModel
  10. [2018.08.09 T1] 数学题
  11. 【英语四六级-必背单词】高中英语单词(C - 1)MP3试听与下载
  12. vbnet 操作autoCAD之 给出矩形如何画内切椭圆
  13. RINEX3文件中的toc,toe,IODE区分和了解
  14. 泰坦以太(以太流论)第五稿 titan_ysl 2020.02.04
  15. u盾如何在计算机上使用方法,u盾在电脑中具体使用操作过程
  16. 新增电子信息专硕,中外合作办学!南京信息工程大学
  17. Spring系列学习之Spring Data Elasticsearch数据访问
  18. 武汉科技大学计算机复试机试,2019年武汉科技大学考研复试及录取工作方案
  19. 搭建游戏平台用哪里的高防服务器比较好
  20. EcShop开发手册

热门文章

  1. Xcel 测试版使用手册
  2. 微信视频号怎么增加浏览量
  3. CSS布局之各种需要掌握的小技巧~
  4. Linux: 李纳斯·托沃兹(Linus Torvalds): “使用KDE”(转)
  5. js将字符串倒叙的方法
  6. 游戏开发核心技术之-存档与读档(1)
  7. 基于改进YOLOv7和CRNN的管道裂缝检测系统(源码&教程)
  8. 仙武:开局神级召唤!(一)
  9. 你有被代理过吗?讲讲开源框架都在用的代理模式
  10. 《码农翻身》原文分章节阅读链接