欢迎访问Python每天3分钟系列。
每天花3分钟时间,学习或温习一个Python知识点。
今天是第026篇:
JSON序列化自定义对象
用Python做JSON的序列化和反序列化是很简单的,就用dumps和loads方法就够了。细节见昨天的3分钟。这是一个例子:
food = [
{'name':'pizza', 'origin':'Italy'},
{'name':'hamburger', 'origin':'UK'},
]
import json
# 把对象转成JSON字符串
food_json = json.dumps(food)
print(f'检查一下food_json的类型,是字符串:{type(food_json)}')
print('打印一下结果:')
print(food_json)
# 把JSON字符串转成对象
from_json = json.loads(food_json)
print(f'看一下类型,应该是列表:{type(from_json)}')
但是如果要转化自定义对象,就没那么简单了。看这个例子:
class Food:
def __init__(self, name, origin, calories, price):
self.name = name
self.origin = origin
self.calories = calories
self.price = price
print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)
recommend = [food1, food2, food3, food4]
import json
recommend_json = json.dumps(recommend, indent=4, ensure_ascii=False)
print(recommend_json)
列表recommend中有4个Food对象。尝试序列化这个列表会出现下面的错误:
TypeError: Object of type Food is not JSON serializable
提示说Food不能序列化!
夸张的说,如何解决这个问题可以说是世纪难题。你看看stackoverflow上这个问题:已经提问了10年了,没有人给出很理想的解决方案。
https://stackoverflow.com/questions/10252010/serializing-class-instance-to-json
理想解决方案
作为一个pythoner,理想的解决方案应该是这样的:
json模块调用一组魔术方法,比如__jsonize__
和__unjsonize__
做JSON的序列化。
自定义对象只要实现了这两个方法,就可以使用dumps和loads做序列化:
class Food:
def __init__(self, name, origin, calories, price):
self.name = name
self.origin = origin
self.calories = calories
self.price = price
def __jsonize__(self):
# 省略...
pass
def __unjsonize__(self):
# 省略...
pass
这是比较pythonic的,但是不知道为什么,json模块并没有这样的魔术方法。所以这个方案行不通。
json的设计者使用了更加Java化的设计思想,dumps函数可以接受一个自定义的序列器类,但这个方法夹里夹气的,这里就不细说了。另外,这种方法只能单独序列化当前对象,不能够序列化包含当前对象的字典,列表等。
普通但常用的解决方案
在实际工作中,很多人就不用json库了,自己定义相应的函数to_json
,比如这样:
class Food:
def __init__(self, name, origin, calories, price):
self.name = name
self.origin = origin
self.calories = calories
self.price = price
def to_json(self):
return self.__dict__
print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)
recommend = [food1, food2, food3, food4]
print(food1.to_json())
这个函数自己定义了to_json函数,等于自己干了。
运行结果如下:
{'name': '胡辣汤', 'origin': '山东', 'calories': 25, 'price': 2}
没什么问题。这个方法在现实中应用也不少。
但是通过这个方法,并不能给recommend列表做序列化,还要自己再手工拼接列表中的数据。和上面的方法一样:这种方法只能单独序列化当前对象,不能够序列化包含当前对象的字典,列表等**。
比较理想的解决方案
下面说一个比较理想的解决方法,说它比较理想是因为:
-
对象可以单独被序列化 -
把对象放在列表,元组,字典中仍然可以直接被序列化 -
把对象传给别的对象,仍然可以被序列化
后面两个优点是前面的方法都无法比拟的。
看例子:
class Food(dict):
def __init__(self, name, origin, calories, price):
dict.__init__(self, name=name, origin=origin, calories=calories, price=price)
self.name = name
self.origin = origin
self.calories = calories
self.price = price
def to_json(self):
return self.__dict__
print('...此处省略2500行推荐算法代码...')
food1 = Food('胡辣汤', '山东', 25, 2)
food2 = Food('油条', '山东', 88, 1)
food3 = Food('豆腐脑', '山东', 35, 2.5)
food4 = Food('焖饼', '山东', 65, 10)
recommend = [food1, food2, food3, food4]
import json
recommend_json = json.dumps(recommend, indent=4, ensure_ascii=False)
print(recommend_json)
运行结果是这样的,完美:
[
{
"name": "胡辣汤",
"origin": "山东",
"calories": 25,
"price": 2
},
{
"name": "油条",
"origin": "山东",
"calories": 88,
"price": 1
},
{
"name": "豆腐脑",
"origin": "山东",
"calories": 35,
"price": 2.5
},
{
"name": "焖饼",
"origin": "山东",
"calories": 65,
"price": 10
}
]
注意代码中的要点:
-
继承dict类,因为dict是可以被序列化的 -
在 __init__
函数中调用dict的构造函数。
总的来说,我对json库的表现是不满意的。把简单的问题搞得复杂化,但是it is what it is。
就像人生一样,很多事情看起来并不合理,但我们心里默默说一句QNMD,还是选择接受它。
评论0