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 類型的轉化對照表:

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

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.推薦

能讀到文章最後,首先得謝謝您對本文的肯定,你的肯定是對我們的最大鼓勵。

你覺本文有幫助,那就點個👍
你有疑問,那就留下您的💬
怕把我弄丟了,那就把我⭐
電腦不方便看,那就把發到你📲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章