在不少狀況下,咱們會有把 Python 對象進行序列化或反序列化的需求,好比開發 REST API,好比一些面向對象化的數據加載和保存,都會應用到這個功能。編程
這裏看一個最基本的例子,這裏給到一個 User 的 Class 定義,再給到一個 data 數據,像這樣:json
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)]
你會怎麼來實現?less
或者我有了上面的列表內容,想要轉成一個 JSON 字符串,變成這樣:ide
[{"name": "Germey", "age": 23}, {"name": "Mike", "age": 20}]
你又會怎麼操做呢?post
另外若是 JSON 數據裏面有各類各樣的髒數據,你須要在初始化時驗證這些字段是否合法,另外 User 這個對象裏面 name、age 的數據類型不一樣,如何針對不一樣的數據類型進行鍼對性的類型轉換,這個你有更好的實現方案嗎?ui
以前我寫過一篇文章這多是 Python 面向對象編程的最佳實踐,介紹過 attrs 和 cattrs 這兩個庫,它們兩者的組合能夠很是方便地實現對象的序列化和反序列化。spa
譬如這樣:3d
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)
運行結果:code
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/,強烈推薦你們用起來。