问题
Sqlalchemy 可以很方便地做ORMapping,把数据库记录映射为业务实体类的实例,例如下面这样:
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
city = db.Column(db.String(50))
addr = db.Column(db.String(200))
pin = db.Column(db.String(10))
if __name__ == '__main__':
student = Student.query.get(1)
但这种实例不方便序列化,比如要想在 Flask Web Service 接口中返回这个对象:
@app.route('/demo/db', methods=['GET'])
def demo_db():
student = Student.query.get(1)
return jsonify(student)
或者是直接转 JSON 字符串:
import json
json.dumps(student)
都会报错:Object of type Student is not JSON serializable
。
方法
在网上搜的话,会有五花八门的答案,大部分都是让你实现某个类似 to_json 的方法,有些根本不管用,有些很麻烦。
其实最简单的解决方案就是:
dataclass
用 python 3.7 引入的 dataclass
装饰器,这个装饰器帮我们实现了对应的方法,可以直接把类变量声明当做实例变量的定义:
from dataclasses import dataclass
@dataclass
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
city = db.Column(db.String(50))
addr = db.Column(db.String(200))
pin = db.Column(db.String(10))
这时候,再调用 jsonify(student)
就不会报错了,但奇怪的是,结果是个空对象:{}
。
原来 dataclass 装饰器“认可”的字段声明,必须有 type annotation,改成下面这样:
from dataclasses import dataclass
@dataclass
class Student(db.Model):
id: int = db.Column(db.Integer, primary_key=True)
name: str = db.Column(db.String(100))
city: str = db.Column(db.String(50))
addr: str = db.Column(db.String(200))
pin: str = db.Column(db.String(10))
输出结果就完美了:
{
addr: "xxx",
city: "bj",
id: 1,
name: "hui",
pin: "xyz"
}
不过jsonify
虽然可用了,json.dumps
依然会报错,最简单的办法,是先把对象转为 dict,再转 json:
import json
from dataclasses import dataclass
print(json.dumps(dataclasses.asdict(student)))
objtyping
在实际使用中,发现 dataclass 还是有局限的:它依赖直接的类 attributes 定义,并且不能获取父类的中的定义,因此像下面的代码:
from dataclasses import dataclass
@dataclass
class BaseObj(db.Model):
id: int = db.Column(db.Integer, primary_key=True)
@dataclass
class Student(BaseObj):
name: str = db.Column(db.String(100))
city: str = db.Column(db.String(50))
addr: str = db.Column(db.String(200))
pin: str = db.Column(db.String(10))
if __name__ == '__main__':
student = Student.query.get(1)
print(json.dumps(dataclasses.asdict(student)))
就会发现输出结果中,缺少了id
字段。
不引入三方库的话,很难通过简洁的代码实现了,这里推荐一个小库:objtyping,对任意实例对象(不需要是dataclass,也无需任何其他装饰),都可以转换为基础类型的dict、list,或者 dict-list 嵌套结构,支持多级嵌套。
首先安装依赖:pip install objtyping
于是上面的Student
对象,就可以这样转换:
from objtyping import to_primitive
student = Student.query.get(1)
print(json.dumps(to_primitive(student)))
可以看到,输出结果中,包含id
字段了。
结论
一句话总结:如果是python 3.7 以上环境,不在意添加 dataclass 装饰器,并且数据对象实例之间没有继承关系,就用 dataclass
方法;如果数据对象定义包含继承关系,或者是老系统,不想一个个添加,就用 objtyping
三方库。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。