溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python 序列化和反序列化庫(kù) MarshMallow 的用法實(shí)例代碼

發(fā)布時(shí)間:2020-10-22 14:47:58 來源:腳本之家 閱讀:251 作者:崔慶才 欄目:開發(fā)技術(shù)

序列化(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)文章希望大家以后多多支持億速云!

向AI問一下細(xì)節(jié)

免責(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)容。

AI