您好,登錄后才能下訂單哦!
序列化(Serialization)與反序列化(Deserialization)是RESTful API 開發(fā)中繞不開的一環(huán),開發(fā)時(shí),序列化與反序列化的功能實(shí)現(xiàn)中通常也會(huì)包含數(shù)據(jù)校驗(yàn)(Validation)相關(guān)的業(yè)務(wù)邏輯。
Marshmallow 是一個(gè)強(qiáng)大的輪子,很好的實(shí)現(xiàn)了 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的使用,將從下面幾個(gè)方面展開,在開始之前,首先需要一個(gè)用于序列化和反序列化的類,我直接與marshmallow官方文檔保持一致了:
class User(object): def __init__(self, name, email): self.name = name self.email = email self.created_at = dt.datetime.now()
在很多情況下,我們會(huì)有把 Python 對(duì)象進(jìn)行序列化或反序列化的需求,比如開發(fā) REST API,比如一些面向?qū)ο蠡臄?shù)據(jù)加載和保存,都會(huì)應(yīng)用到這個(gè)功能。
比如這里看一個(gè)最基本的例子,這里給到一個(gè) User 的 Class 定義,再給到一個(gè) data 數(shù)據(jù),像這樣:
class User(object): def __init__(self, name, age): self.name = name self.age = age data = [{ 'name': 'Germey', 'age': 23 }, { 'name': 'Mike', 'age': 20 }]
現(xiàn)在我要把這個(gè) data 快速轉(zhuǎn)成 User 組成的數(shù)組,變成這樣:
[User(name='Germey', age=23), User(name='Mike', age=20)]
你會(huì)怎么來實(shí)現(xiàn)?
或者我有了上面的列表內(nèi)容,想要轉(zhuǎn)成一個(gè) JSON 字符串,變成這樣:
[{"name": "Germey", "age": 23}, {"name": "Mike", "age": 20}]
你又會(huì)怎么操作呢?
另外如果 JSON 數(shù)據(jù)里面有各種各樣的臟數(shù)據(jù),你需要在初始化時(shí)驗(yàn)證這些字段是否合法,另外 User 這個(gè)對(duì)象里面 name、age 的數(shù)據(jù)類型不同,如何針對(duì)不同的數(shù)據(jù)類型進(jìn)行針對(duì)性的類型轉(zhuǎn)換,這個(gè)你有更好的實(shí)現(xiàn)方案嗎?
初步思路
之前我寫過一篇文章介紹過 attrs 和 cattrs 這兩個(gè)庫(kù),它們二者的組合可以非常方便地實(shí)現(xiàn)對(duì)象的序列化和反序列化。
譬如這樣:
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)
運(yùn)行結(jié)果:
user User(name='Germey', age=23) json {'name': 'Germey', 'age': 23}
好,這里我們通過 attrs 和 cattrs 這兩個(gè)庫(kù)來實(shí)現(xiàn)了單個(gè)對(duì)象的轉(zhuǎn)換。
首先我們要肯定一下 attrs 這個(gè)庫(kù),它可以極大地簡(jiǎn)化 Python 類的定義,同時(shí)每個(gè)字段可以定義多種數(shù)據(jù)類型。
但 cattrs 這個(gè)庫(kù)就相對(duì)弱一些了,如果把 data 換成數(shù)組,用 cattrs 還是不怎么好轉(zhuǎn)換的,另外它的 structure 和 unstructure 在某些情景下容錯(cuò)能力較差,所以對(duì)于上面的需求,用這兩個(gè)庫(kù)搭配起來并不是一個(gè)最優(yōu)的解決方案。
另外數(shù)據(jù)的校驗(yàn)也是一個(gè)問題,attrs 雖然提供了 validator 的參數(shù),但對(duì)于多種類型的數(shù)據(jù)處理的支持并沒有那么強(qiáng)大。
所以,我們想要尋求一個(gè)更優(yōu)的解決方案。
更優(yōu)雅的方案
這里推薦一個(gè)庫(kù),叫做 marshmallow,它是專門用來支持 Python 對(duì)象和原生數(shù)據(jù)相互轉(zhuǎn)換的庫(kù),如實(shí)現(xiàn) object -> dict,objects -> list, string -> dict, string -> list 等的轉(zhuǎn)換功能,另外它還提供了非常豐富的數(shù)據(jù)類型轉(zhuǎn)換和校驗(yàn) API,幫助我們快速實(shí)現(xiàn)數(shù)據(jù)的轉(zhuǎn)換。
要使用 marshmallow 這個(gè)庫(kù),需要先安裝下:
pip3 install marshmallow
好了之后,我們?cè)谥暗幕A(chǔ)上定義一個(gè) Schema,如下:
class UserSchema(Schema): name = fields.Str() age = fields.Integer() @post_load def make(self, data, **kwargs): return User(**data)
還是之前的數(shù)據(jù):
data = [{ 'name': 'Germey', 'age': 23 }, { 'name': 'Mike', 'age': 20 }]
這時(shí)候我們只需要調(diào)用 Schema 的 load 事件就好了:
schema = UserSchema() users = schema.load(data, many=True) print(users)
輸出結(jié)果如下:
[User(name='Germey', age=23), User(name='Mike', age=20)]
這樣,我們非常輕松地完成了 JSON 到 User List 的轉(zhuǎn)換。
有人說,如果是單個(gè)數(shù)據(jù)怎么辦呢,只需要把 load 方法的 many 參數(shù)去掉即可:
data = { 'name': 'Germey', 'age': 23 } schema = UserSchema() user = schema.load(data) print(user)
輸出結(jié)果:
User(name='Germey', age=23)
當(dāng)然,這僅僅是一個(gè)反序列化操作,我們還可以正向進(jìn)行序列化,以及使用各種各樣的驗(yàn)證條件。
下面我們?cè)賮砜纯窗伞?/p>
更方便的序列化
上面的例子我們實(shí)現(xiàn)了序列化操作,輸出了 users 為:
[User(name='Germey', age=23), User(name='Mike', age=20)]
有了這個(gè)數(shù)據(jù),我們也能輕松實(shí)現(xiàn)序列化操作。
序列化操作,使用 dump 方法即可
result = schema.dump(users, many=True) print('result', result)
運(yùn)行結(jié)果如下:
result [{'age': 23, 'name': 'Germey'}, {'age': 20, 'name': 'Mike'}]
由于是 List,所以 dump 方法需要加一個(gè)參數(shù) many 為 True。
當(dāng)然對(duì)于單個(gè)對(duì)象,直接使用 dump 同樣是可以的:
result = schema.dump(user) print('result', result)
運(yùn)行結(jié)果如下:
result {'name': 'Germey', 'age': 23}
這樣的話,單個(gè)、多個(gè)對(duì)象的序列化也不再是難事。
經(jīng)過上面的操作,我們完成了 object 到 dict 或 list 的轉(zhuǎn)換,即:
object <-> dict objects <-> list
驗(yàn)證
當(dāng)然,上面的功能其實(shí)并不足以讓你覺得 marshmallow 有多么了不起,其實(shí)就是一個(gè)對(duì)象到基本數(shù)據(jù)的轉(zhuǎn)換嘛。但肯定不止這些,marshmallow 還提供了更加強(qiáng)大啊功能,比如說驗(yàn)證,Validation。
比如這里我們將 age 這個(gè)字段設(shè)置為 hello,它無法被轉(zhuǎn)換成數(shù)值類型,所以肯定會(huì)報(bào)錯(cuò),樣例如下:
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)
這里如果加載報(bào)錯(cuò),我們可以直接拿到 Error 的 messages 和 valid_data 對(duì)象,它包含了錯(cuò)誤的信息和正確的字段結(jié)果,運(yùn)行結(jié)果如下:
e.message {'age': ['Not a valid integer.']} e.valid_data {'name': 'Germey'}
因此,比如我們想要開發(fā)一個(gè)功能,比如用戶注冊(cè),表單信息就是提交過來的 data,我們只需要過一遍 Validation,就可以輕松得知哪些數(shù)據(jù)符合要求,哪些不符合要求,接著再進(jìn)一步進(jìn)行處理。
當(dāng)然驗(yàn)證功能肯定不止這一些,我們?cè)賮砀惺芤幌铝硪粋€(gè)示例:
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 字段,我們分別校驗(yàn)了 name、permission、age 三個(gè)字段,校驗(yàn)方式各不相同。
如 name 我們要判斷其最小值為 1,則使用了 Length 對(duì)象。permission 必須要是幾個(gè)字符串之一,這里又使用了 OneOf 對(duì)象,age 又必須是介于某個(gè)范圍之間,這里就使用了 Range 對(duì)象。
下面我們故意傳入一些錯(cuò)誤的數(shù)據(jù),看下運(yùn)行結(jié)果:
{'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.']}
可以看到,這里也返回了數(shù)據(jù)驗(yàn)證的結(jié)果,對(duì)于不符合條件的字段,一一進(jìn)行說明。
另外我們也可以自定義驗(yàn)證方法:
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)
通過自定義方法,同樣可以實(shí)現(xiàn)更靈活的驗(yàn)證,運(yùn)行結(jié)果:
{'quantity': ['Quantity must not be greater than 30.']}
對(duì)于上面的例子,還有更優(yōu)雅的寫法:
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 修飾符,使得代碼的書寫更加簡(jiǎn)潔。
必填字段
如果要想定義必填字段,只需要在 fields 里面加入 required 參數(shù)并設(shè)置為 True 即可,另外我們還可以自定義錯(cuò)誤信息,使用 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)
默認(rèn)字段
對(duì)于序列化和反序列化字段,marshmallow 還提供了默認(rèn)值,而且區(qū)分得非常清楚!如 missing 則是在反序列化時(shí)自動(dòng)填充的數(shù)據(jù),default 則是在序列化時(shí)自動(dòng)填充的數(shù)據(jù)。
例如:
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({}))
這里我們都是定義的空數(shù)據(jù),分別進(jìn)行序列化和反序列化,運(yùn)行結(jié)果如下:
{'id': UUID('06aa384a-570c-11ea-9869-a0999b0d6843')} {'birthdate': '2017-09-29T00:00:00'}
可以看到,在沒有真實(shí)值的情況下,序列化和反序列化都是用了默認(rèn)值。
這個(gè)真的是解決了我之前在 cattrs 序列化和反序列化時(shí)候的痛點(diǎn)啊!
指定屬性名
在序列化時(shí),Schema 對(duì)象會(huì)默認(rèn)使用和自身定義相同的 fields 屬性名,當(dāng)然也可以自定義,如:
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)
運(yùn)行結(jié)果如下:
{'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)
運(yùn)行結(jié)果如下:
{'name': u'Mike', 'email': 'foo@bar.com'}
嵌套屬性
對(duì)于嵌套屬性,marshmallow 當(dāng)然也不在話下,這也是讓我覺得 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)
這樣我們就能充分利用好對(duì)象關(guān)聯(lián)外鍵來方便地實(shí)現(xiàn)很多關(guān)聯(lián)功能。
以上介紹的內(nèi)容基本算在日常的使用中是夠用了,當(dāng)然以上都是一些基本的示例,對(duì)于更多功能,可以參考 marchmallow 的官方文檔: https://marshmallow.readthedocs.io/en/stable/,強(qiáng)烈推薦大家用起來 。
總結(jié)
到此這篇關(guān)于Python 序列化和反序列化庫(kù) MarshMallow 的用法實(shí)例代碼的文章就介紹到這了,更多相關(guān)Python 序列化和反序列化庫(kù) MarshMallow 內(nèi)容請(qǐng)搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。