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