python-object与dict互转

先举个使用场景:python处理完业务逻辑,将一个结果封装成统一格式对象,然后json格式返回给客户端。

本文概要

  1. python标准类型有哪些?哪些是可变和不可变的,如何转化?
  2. obj转dict有哪些方式?各有啥特点?
  3. dict又如何转化为obj呢?
  4. 上面的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千万的量,测试下它们性能

方式耗时性能
__dict__0.7秒
dict函数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千万的量测了下它们性能

方式耗时性能
__dict__0.7秒
dict函数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_dicto_obj就组成了obj和dict互转的工具类型了。

4.peewee转化model

如果项目中使用的了peewee的话,那么可以使用model_to_dictdict_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 类型的转化对照表:

PythonJSON
dictobject
list, tuplearray
str, unicodestring
int, long, floatnumber
Truetrue
Falsefalse
Nonenull

json 类型转换到 python 的类型对照表:

JSONPython
objectdict
arraylist
stringunicode
number (int)int, long
number (real)float
trueTrue
falseFalse
nullNone

那下面我们来看下如何处理上我们上面的场景

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

importclass 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

6.推荐

能读到文章最后,首先得谢谢您对本文的肯定,你的肯定是对我们的最大鼓励。

你觉本文有帮助,那就点个👍
你有疑问,那就留下您的💬
怕把我弄丢了,那就把我⭐
电脑不方便看,那就把发到你📲

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值