先举个使用场景:python处理完业务逻辑,将一个结果封装成统一格式对象,然后json格式返回给客户端。
本文概要
- python标准类型有哪些?哪些是可变和不可变的,如何转化?
- obj转dict有哪些方式?各有啥特点?
- dict又如何转化为obj呢?
- 上面的json格式化对象场景,如何实现?
1.python类型
标准数据类型
- 不可变
- Number(数字) int、float、bool、complex
- String(字符串)
- Tuple(元组)
- 可变
- List(列表)
- Set(集合)
- Dictionary(字典)
相互转化
函数 | 描述 |
int(x) | 将x转换为一个整数 |
float(x) | 将x转换到一个浮点数 |
complex(real) | 创建一个复数 |
str(x) | 将对象 x 转换为字符串 |
repr(x) | 将对象 x 转换为表达式字符串 |
eval(str) | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
tuple(s) | 将序列 s 转换为一个元组 |
list(s) | 将序列 s 转换为一个列表 |
set(s) | 转换为可变集合 |
dict(d) | 创建一个字典。d 必须是一个 (key, value)元组序列。 |
frozenset(s) | 转换为不可变集合 |
chr(x) | 将一个整数转换为一个字符 |
ord(x) | 将一个字符转换为它的整数值 |
hex(x) | 将一个整数转换为一个十六进制字符串 |
oct(x) | 将一个整数转换为一个八进制字符串 |
2.对象转dict
python的dict只能采用obj["name"]
的方式来写入和读取
python的ojb只能采取obj.name
的方式来读取和写入
三种方式
__dict__
在
__init__
方法和对象显示赋值的属性才会转化
- dict函数
需要定义
keys
和__getitem__
方法,所有属性都会转化dict
- 取出对象里属性名和值,塞到dict中
需要遍历对象的属性
三种方式以1千万的量,测试下它们性能
方式 | 耗时 | 性能 |
| 0.7秒 | 高 |
| 11秒 | 中 |
| 3分12秒 | 低 |
直接取属性值最快,自定义
__getitem__
方法次之,遍历属性最慢
如何对__init__
、__dict__
、vars
、__getitem__
不太了解的,可以分别查看下这两篇文章
2-1.dict
# 对象类
class User:
is_admin = False
desc = ''
def __init__(self, name) -> None:
self.name = name
self.age = 1
# 测试方法
class MyTestCase(unittest.TestCase):
def test_user(self):
user = User('iworkh')
user.is_admin = True
# 只有塞值的时候,才会转化为dic
# desc没有塞值,dic中没有
print(user.__dict__)
pass
结果:
{‘name’: ‘iworkh’, ‘age’: 1, ‘is_admin’: True}
name和age
在init函数里定义,is_admin
通过对象塞值了,三个属性值都显示。
而desc
不在init函数中,也没有塞值,没有显示
2-2.dict函数
# 对象类
class Product:
price = 10
desc = ''
def __init__(self, name) -> None:
self.name = name
def keys(self):
'''当对实例化对象使用dict(obj)的时候, 会调用这个方法,这里定义了字典的键, 其对应的值将以obj['name']的形式取,
但是对象是不可以以这种方式取值的, 为了支持这种取值, 可以为类增加一个方法'''
return ('name', 'price', 'desc')
def __getitem__(self, item):
'''内置方法, 当使用obj['name']的形式的时候, 将调用这个方法, 这里返回的结果就是值'''
return getattr(self, item)
# 测试方法
class MyTestCase(unittest.TestCase):
def test_product(self):
product = Product('book')
print(dict(product))
pass
结果:
{‘name’: ‘book’, ‘price’: 10, ‘desc’: ‘’}
都显示,但是需要定义keys
和__getitem__
函数,上卖弄都有注释就不解释了
2-3.属性方式
工具类
class objDictTool:
@staticmethod
def to_dic(obj):
dic = {}
for fieldkey in dir(obj):
fieldvaule = getattr(obj, fieldkey)
if not fieldkey.startswith("__") and not callable(fieldvaule) and not fieldkey.startswith("_"):
dic[fieldkey] = fieldvaule
return dic
这只取了不是
__
和_
开头的属性,如需要可以修改代码
# 对象类
class Order:
order_no: str = ''
desc = ''
def __init__(self, name) -> None:
self.name = name
self.age = 1
# 测试方法
class MyTestCase(unittest.TestCase):
def test_order_by_attr(self):
user = User('iworkh')
dic = objDictTool.to_dic(user)
print(dic)
pass
结果:
{‘age’: 1, ‘desc’: ‘’, ‘is_admin’: False, ‘name’: ‘iworkh’}
2-4.总结
三种方式各特点,使用时要注意不同的场合
三种方法以1千万的量测了下它们性能
方式 | 耗时 | 性能 |
| 0.7秒 | 高 |
| 11秒 | 中 |
| 3分12秒 | 低 |
使用场景:
- 如果您能保证对的属性值都塞了,或者没有的值属性不转化到dict也没关系,那么可以选择
__dict__
方式 - 如果要obj转dict的对象不多,整个项目中就几个类对应的对象,而且要全部属性(即使没塞值的,也要转到dict中),那么可以选择
dict函数
方式 - 如果要obj转dict的类很多,但是不会频繁转化(因为效率低),那么可以选择
属性
方式。
3.dict转对象
工具类
class objDictTool:
@staticmethod
def to_obj(obj: object, **data):
obj.__dict__.update(data)
通过对象的
__dict__
属性,然后调用update
更新方法,将data的值赋给obj
class MyTestCase(unittest.TestCase):
def test_dict_to_obj(self):
product_dic = {'name': 'python', 'price': 10, 'desc': 'python对象和dic转化'}
product_obj = Product('test')
objDictTool.to_obj(product_obj, **product_dic)
print("名称: {}, 价格: {}, 描述: {}".format(product_obj.name, product_obj.price, product_obj.desc))
pass
结果:
名称: python, 价格: 10, 描述: python对象和dic转化
objDictTool里的一个to_dic
和to_obj
就组成了obj和dict互转的工具类型了。
4.peewee转化model
如果项目中使用的了peewee的话,那么可以使用model_to_dict
和dict_to_model
,将model和dict项目转化
model定义
class UserModel(BaseModel):
user_name = CharField(max_length=20, null=True, verbose_name="用户名")
nick_name = CharField(max_length=20, null=True, verbose_name="昵称")
password = CharField(max_length=20, null=True, verbose_name="密码")
phone = CharField(max_length=11, index=True, unique=True, verbose_name="手机号码")
sex = CharField(max_length=1, choices=SEXES, verbose_name="性别")
class Meta:
table_name = 'T_ACTION_USER'
转化使用
from playhouse.shortcuts import model_to_dict
class UserService:
async def create(self, userModel):
await objects.create(UserModel, **model_to_dict(userModel))
注意:
dict_to_model使用和model_to_dict类似,注意:项目项目使用peewee才可以用
5.应用
我们来解决,文章开始抛出的使用场景:
python处理完业务逻辑,将一个结果封装成统一格式对象返回给前台,以json格式。json格式化需要是python的基本。
比如我们需要每次返回的结果都下:
- 错误时:success为false, errorCode和message有值
- 正常时:success为true, data有的数据
json格式
{
"success": true,
"errorCode": 0,
"message": "",
"data": {
"id": 2,
"created_date": "2020-06-24T14:47:21",
"update_date": "2020-06-24T14:47:21",
"user_name": "iworkh",
"nick_name": "沐雨云楼"
}
}
实体类
class Comment:
title: str = ''
author: str = ''
desc: str = ''
create_time: datetime = datetime.now()
5-1.json序列化
开始之前,还是先讲得解下json的序列化知识
使用 JSON 函数需要导入 json 库:import json。
函数 | 描述 |
json.dumps | 将 Python 对象编码成 JSON 字符串 |
json.loads | 将已编码的 JSON 字符串解码为 Python 对象 |
python 原始类型向 json 类型的转化对照表:
Python | JSON |
dict | object |
list, tuple | array |
str, unicode | string |
int, long, float | number |
True | true |
False | false |
None | null |
json 类型转换到 python 的类型对照表:
JSON | Python |
object | dict |
array | list |
string | unicode |
number (int) | int, long |
number (real) | float |
true | True |
false | False |
null | None |
那下面我们来看下如何处理上我们上面的场景
5-2.简单版
警告
注意下面代码层层迭代过程,从出错到正确的演变,最终达到可用代码。不要只追求结果,要知道其步步演变的过程。
测试类
class MyTestCase(unittest.TestCase):
def test_json(self):
# 返回json时,序列化对象要是python原始类型:
# dict, list, tuple, str, unicode, int, long, float, True, False, None
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
result = {'success': True, 'data': comment}
json_str = json.dumps(result)
print(json_str)
pass
思考有没问题?能得到预期的结果嘛?
结果:
报错:
Object of type Comment is not JSON serializable
如果有java基础肯定直到,一个对象要虚拟化得实现serializable
接口,为什么?
可以查看这篇文章 java序列化
解决办法:
就是本文前面将的如何将obj转化为dict的知识了,修改代码如下
class MyTestCase(unittest.TestCase):
def test_json(self):
# 返回json时,序列化对象要是 python原始类型:
# dict, list, tuple, str, unicode, int, long, float, True, False, None
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
result = {'success': True, 'data': comment}
json_str = json.dumps(result, default=lambda obj: obj.__dict__, sort_keys=True, indent=4, ensure_ascii=False)
print(json_str)
pass
dumps几个参数说明
-
default
: 指定如何任意一个对象变成一个可序列为JSON的对象的函数,这使用的是lambda
表达式 -
sort_keys
: 安装对象的属性字母顺序排序 -
indent
: json格式化时,使用的空格数 -
ensure_ascii
: False不转换为asccii码
结果
{
"data": {
"author": "沐雨云楼",
"desc": "怎么玩呢?",
"title": "python工具类-obj转dict"
},
"success": true
}
下面的写法跟上面是完全等效的
class MyTestCase(unittest.TestCase):
def test_json(self):
# 返回json时,序列化对象要是 python原始类型:
# dict, list, tuple, str, unicode, int, long, float, True, False, None
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
result = {'success': True, 'data': comment.__dict__}
json_str = json.dumps(result, sort_keys=True, indent=4, ensure_ascii=False)
print(json_str)
pass
不使用
default
,将data设置为dict
我们有没发现,这种方式,如果没有塞值的话,那么dict里是没有,比如这里create_time
,json格式化没出来
塞create_time值
class MyTestCase(unittest.TestCase):
def test_json(self):
# 返回json时,序列化对象要是 python原始类型:
# dict, list, tuple, str, unicode, int, long, float, True, False, None
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
comment.create_time=datetime.now()
result = {'success': True, 'data': comment}
json_str = json.dumps(result, default=lambda obj: obj.__dict__, sort_keys=True, indent=4, ensure_ascii=False)
print(json_str)
pass
这样create_time
时间就能出来了嘛?
结果:
不好意思,还是没有。报错了: ‘datetime.datetime’ object has no attribute ‘dict’
json格式化,要对时间类型特殊处理
文件名json_tool.py的工具类
from datetime import datetime, date
def json_serial(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type {} s not serializable".format(type(obj)))
对对象是datetime和date特殊处理
修改代码如下:
class MyTestCase(unittest.TestCase):
def test_json(self):
# 返回json时,序列化对象要是 python原始类型:
# dict, list, tuple, str, unicode, int, long, float, True, False, None
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
comment.create_time = datetime.now()
result = {'success': True, 'data': comment.__dict__}
json_str = json.dumps(result, default=json_serial, sort_keys=True, indent=4, ensure_ascii=False)
print(json_str)
pass
这就没有问题了,最后结果也包含了时间
{
"data": {
"author": "沐雨云楼",
"create_time": "2020-06-24T22:30:56.890901",
"desc": "怎么玩呢?",
"title": "python工具类-obj转dict"
},
"success": true
}
5-3.封装对象版
我们先上面简化的缺点,主要以这两行代码来说:
简单版代码片段
result = {'success': True, 'data': comment.__dict__}
json_str = json.dumps(result, default=json_serial, sort_keys=True, indent=4, ensure_ascii=False)
缺点:
- result是自己定义的一个
{}
dict,每个人可能不一样,而且可能写错,比如success
写错了sucesss
,那么跟前台接受数据格式对比不上;data
换成了result
等,所有用到的地方都得修改 - json.dumps的参数,每次都要写一长串
因此,需要对result
进一步封装,封装一个对象JsonDataResult
,将对象转为json再传递。
将result封装为JsonDataResult
import json
import 省略
class JsonDataResult:
success: bool = False
errorCode: int = 0
message: str = ''
data: dict = None
def keys(self):
return ('success', 'errorCode', 'message', 'data')
def __getitem__(self, item):
return getattr(self, item)
def to_json(self):
return json.dumps(dict(self), default=json_serial, sort_keys=True, indent=4, ensure_ascii=False)
思考:为啥这用dict函数
方式来将obj转为dict?
因为: 只有JsonDataResult这一个对象转化,且返回给前台都保持数据结构一样,不管有没塞值。
使用代码测试
class MyTestCase(unittest.TestCase):
def test_json_data_result(self):
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
result = JsonDataResult()
result.success = True
result.data = objDictTool.to_dic(comment)
print(result.to_json())
pass
注意几点:
- 对象都使用
JsonDataResult
封装的类,不用担心变量写错了,而且大家都一样对象,返回结构是一样。 -
result.to_json
里的to_json
方法,都调用这个函数,不用每次写一大段,修改的时候,也只要修改一处即可 - 只要保证data的能够被json转化即可(data如果是类对象,记得要转化为dict类型,具体要看需求,选择obj转dict哪一种方式。如项目中使用了peewee的话,那么可以使用
model_to_dict
)
结果:
{
"data": {
"author": "沐雨云楼",
"create_time": "2020-06-25T10:17:43.412471",
"desc": "怎么玩呢?",
"title": "python工具类-obj转dict"
},
"errorCode": 0,
"message": "",
"success": true
}
5-4.代码
如需要源码,可以查看。(关键代码都贴在文章里,不用看也行)
下面贴出最终版的代码片段,供阅读
json_tool.py
from datetime import datetime, date
def json_serial(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type {} s not serializable".format(type(obj)))
obj_dict_tool.py
class objDictTool:
@staticmethod
def to_dic(obj):
dic = {}
for fieldkey in dir(obj):
fieldvaule = getattr(obj, fieldkey)
if not fieldkey.startswith("__") and not callable(fieldvaule) and not fieldkey.startswith("_"):
dic[fieldkey] = fieldvaule
return dic
@staticmethod
def to_obj(obj: object, **data):
obj.__dict__.update(data)
json_data_result.py
import 略
class JsonDataResult:
success: bool = False
errorCode: int = 0
message: str = ''
data: dict = None
def keys(self):
return ('success', 'errorCode', 'message', 'data')
def __getitem__(self, item):
return getattr(self, item)
def to_json(self):
return json.dumps(dict(self), default=json_serial, sort_keys=True, indent=4, ensure_ascii=False)
测试类
class Comment:
title: str = ''
author: str = ''
desc: str = ''
create_time: datetime = datetime.now()
class MyTestCase(unittest.TestCase):
def test_json_data_result(self):
comment = Comment()
comment.title = 'python工具类-obj转dict'
comment.author = '沐雨云楼'
comment.desc = '怎么玩呢?'
result = JsonDataResult()
result.success = True
result.data = objDictTool.to_dic(comment)
print(result.to_json())
pass