物件序列化


在資料儲存的上下文中,序列化是將資料結構或物件狀態轉換為可以儲存(例如,在檔案或儲存緩衝器中)或稍後傳輸和重構的格式的過程。

在序列化中,物件被轉換為可以儲存的格式,以便以後能夠對其進行反序列化並從序列化格式重新建立原始物件。

Pickle

Pickling是將Python物件層次結構轉換為要寫入檔案的位元組流(通常不是人類可讀的)的過程,這也稱為序列化。Unpickling是反向操作,將位元組流轉換回工作的Python物件層次結構。

Pickle是操作上最簡單的儲存物件的方法。 Python Pickle模組是一種物件導向的方式,可以直接以特殊的儲存格式儲存物件。

它能做什麼?

  • Pickle可以非常輕鬆地儲存和複製字典和列表。
  • 儲存物件屬性並將它們還原到同一個狀態。

Pickle不能做什麼?

  • 它不儲存物件程式碼,只有它的屬性值。
  • 它無法儲存檔案控制代碼或連線通訊端。

簡而言之,pickling是一種在檔案中儲存和檢索資料變數的方法,其中變數可以是列表,類等。

要使用Pickle,必須要 -

  • 匯入Pickle
  • 將變數寫入檔案

例如

pickle.dump(mystring, outfile, protocol),

其中第三個引數:protocol是可選的,

要使用unpickling,必須要 -

  • 匯入Pickle
  • 將變數寫入檔案

例如

myString = pickle.load(inputfile)

pickle 介面提供四種不同的方法。

  • dump() - 序列化到開啟的檔案(類檔案物件)。
  • dumps() - 序列化為字串
  • load() - 從類似開放的物件反序列化。
  • loads() - 從字串反序列化。

基於以上程式,下面是「pickle」的一個例子。

import pickle

class Animal:
    def __init__(self, number_of_legs, color):
        self.number_of_legs = number_of_legs
        self.color = color

class Cat(Animal):
    def __init__(self, color):
        Animal.__init__(self, 4, color)

pussy  = Cat('White')
print(str.format('My Cat pussy is {0} and has {1} legs', pussy.color, pussy.number_of_legs))

pickled_pussy = pickle.dumps(pussy)

print("Would you like to see her pickled? Here she is - ")

print(pickled_pussy)

執行上面範例程式碼,得到以下結果 -

My Cat pussy is White and has 4 legs
Would you like to see her pickled? Here she is - 
b'\x80\x03c__main__\nCat\nq\x00)\x81q\x01}q\x02(X\x0e\x00\x00\x00number_of_legsq\x03K\x04X\x05\x00\x00\x00colorq\x04X\x05\x00\x00\x00Whiteq\x05ub.'

因此,在上面的範例中,建立了一個Cat類的範例,然後將它pickled,將「Cat」範例轉換為一個簡單的位元組陣列。

這樣,可以輕鬆地將位元組陣列儲存在二進位制檔案或資料庫欄位中,並在以後從儲存支援將其恢復為原始格式。

此外,如果要建立帶有pickle物件的檔案,可以使用dump()方法(而不是dumps *()* one)同時傳遞開啟的二進位制檔案,並且pickling結果將自動儲存在檔案中。

[….]
binary_file = open(my_pickled_Pussy.bin', mode='wb')
my_pickled_Pussy = pickle.dump(Pussy, binary_file)
binary_file.close()

Unpickling

採用二進位制陣列並將其轉換為物件層次結構的過程稱為unpickling

通過使用pickle模組的load()函式完成unpickling過程,並從簡單的位元組陣列返回一個完整的物件層次結構。

在前面的例子中使用load函式。

import pickle

class Animal:
    def __init__(self, number_of_legs, color):
        self.number_of_legs = number_of_legs
        self.color = color

class Cat(Animal):
    def __init__(self, color):
        Animal.__init__(self, 4, color)

pussy  = Cat('White')
print(str.format('My Cat pussy is {0} and has {1} legs', pussy.color, pussy.number_of_legs))
pickled_pussy = pickle.dumps(pussy)
# print("Would you like to see her pickled? Here she is - ")
# print(pickled_pussy)
pcat = pickle.loads(pickled_pussy)
pcat.color = "black"
print(str.format("PCat is {0}", pcat.color))
print(str.format("Pussy is {0}", pussy.color))

執行上面範例程式碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
My Cat pussy is White and has 4 legs
PCat is black
Pussy is White

JSON

JSON(JavaScript Object Notation)已經成為Python標準庫的一部分,是一種輕量級的資料交換格式。 人類很容易讀寫。 它很容易解析和生成。

由於其簡單性,JSON是我們儲存和交換資料的一種方式,它通過其JSON語法實現,並在許多Web應用程式中使用。 因為它是人類可讀的格式,這可能是在資料傳輸中使用它的原因之一,除了它在使用API時的有效性。

JSON格式資料的範例如下 -

{"EmployID": 40203, "Name": "Maxsu", "Age":54, "isEmployed": True}

Python使用的模組是JSON模組使得使用Json檔案變得簡單。應該在Python安裝中包含(內建)此模組。

下面來看看如何將Python字典轉換為JSON並將其寫入文字檔案。

JSON到Python

讀JSON意味著將JSON轉換為Python值(物件)。 json庫將JSON解析為Python中的字典或列表。要做到這一點,可使用loads()函式(從字串載入),如下所示 -

import json

json_data = '{"EmployeeID": 10010, "EmployeeName": "Maxsu", "Department":"技術部"}'
json2python = json.loads(json_data)

執行上面範例程式碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
{'EmployeeID': 10010, 'EmployeeName': 'Maxsu', 'Department': '技術部'}

下面是一個範例json檔案:data1.json

{"menu": {
   "id": "file",
   "value": "File",
   "popup": {
      "menuitem": [
         {"value": "New", "onclick": "CreateNewDoc()"},
         {"value": "Open", "onclick": "OpenDoc()"},
         {"value": "Close", "onclick": "CloseDoc()"}
      ]
   }
}}

上面的內容(data1.json)看起來像傳統的字典。 可以使用pickle來儲存這個檔案,但它的輸出不是人類可讀的形式。

JSON(Java Script Object Notification)是一種非常簡單的格式,這也是它受歡迎的原因之一。 現在通過以下程式來列印上面json檔案的輸出。

import json

with open('data1.json') as f:
    config = json.load(f)
print(config)
print('+++++++++++++++++++++++++++++++++++++++++++++++++')
print(type(config))
print('+++++++++++++++++++++++++++++++++++++++++++++++++')
config['newkey'] = 10086

with open("data2.json", 'w') as wf:
    json.dump(config, wf)

print(config)

執行上面範例程式碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
{'menu': {'id': 'file', 'value': 'File', 'popup': {'menuitem': [{'value': 'New', 'onclick': 'CreateNewDoc()'}, {'value': 'Open', 'onclick': 'OpenDoc()'}, {'value': 'Close', 'onclick': 'CloseDoc()'}]}}}
+++++++++++++++++++++++++++++++++++++++++++++++++
<class 'dict'>
+++++++++++++++++++++++++++++++++++++++++++++++++
{'menu': {'id': 'file', 'value': 'File', 'popup': {'menuitem': [{'value': 'New', 'onclick': 'CreateNewDoc()'}, {'value': 'Open', 'onclick': 'OpenDoc()'}, {'value': 'Close', 'onclick': 'CloseDoc()'}]}}, 'newkey': 10086}

Process finished with exit code 0

上面程式開啟json檔案(data1.json)進行讀取,獲取檔案處理程式並傳遞給json.load並獲取物件。 當列印物件的輸出時,可以看到它與json檔案相同。 雖然物件的型別是字典,它此時是Python物件。上面載入json檔案,新增另一個鍵值對並將其寫回到到另一個json檔案中。 現在,如果開啟檔案:data2.json,它看起來不同。與之前看到的格式不同。

要想使輸出看起來相同(人類可讀的格式),將幾個引數新增到程式的最後一行,參考如上程式碼 -

json.dump(conf, fh, indent = 4, separators = (',', ': '))

pickle類似,可以使用轉儲列印字串併使用load載入。 以下是一個例子,

YAML

YAML可能是所有程式設計語言中最人性化的資料序列化標準。

Python yaml模組叫作:pyaml

YAML是JSON的替代品 -

  • 人類可讀的程式碼 - YAML是人類可讀的格式,因此即使其首頁內容也顯示在YAML中以表明這一點。
  • 緊湊的程式碼 - 在YAML中,我們使用空格縮排來表示結構而不是括號。
  • 關係資料的語法 - 對於內部參照,使用錨點()和別名(*)。
  • 廣泛使用的一個領域是檢視/編輯資料結構 - 例如組態檔案,偵錯期間的轉儲和文件頭。

安裝YAML

由於yaml不是內建模組,需要手動安裝它。 在Windows機器上安裝yaml的最佳方法是通過pip。 在windows終端上執行以下命令安裝yaml,

# Windows
pip install pyaml
# *nix and Mac
sudo pip install pyaml

在執行上面的命令時,將根據當前的最新版本顯示如下所示的內容。

Collecting pyaml
Using cached pyaml-17.12.1-py2.py3-none-any.whl
Collecting PyYAML (from pyaml)
Using cached PyYAML-3.12.tar.gz
Installing collected packages: PyYAML, pyaml
Running setup.py install for PyYAML ... done
Successfully installed PyYAML-3.12 pyaml-17.12.1

要測試它,轉到Python shell並匯入yaml模組,匯入yaml,如果沒有提示錯誤,那麼就是安裝成功了。

安裝pyaml後,讓我們看下面的程式碼,檔案:script_yaml1.py -

import yaml

mydict = {'a': 2, 'b': 3, 'c': 6}
mylist = [1, 2, 3, 4, 5, 6]
mytuple = ('a', 'b', 'c', 'x', 'y', 'z')

print(yaml.dump(mydict, default_flow_style=False))
print(yaml.dump(mylist, default_flow_style=False))
print(yaml.dump(mytuple, default_flow_style=False))

上面建立了三種不同的資料結構,字典,列表和元組。 在每個結構上,使用yaml.dump
執行上面範例程式碼後,得到以下結果 -

a: 2
b: 3
c: 6

- 1
- 2
- 3
- 4
- 5
- 6

!!python/tuple
- a
- b
- c
- x
- y
- z

字典輸出看起來很乾淨,即它們的值。

用於分隔不同物件的空白區域。

列表標有破折號( - )

元組首先用!!python/tuple表示,然後以與列表相同的格式表示。

載入yaml檔案範例

所以,假設有一個yaml檔案,如下所示 -

---
# An employee record
name: Raagvendra Joshi
job: Developer
skill: Oracle
employed: True
foods:
   - Apple
   - Orange
   - Strawberry
   - Mango
languages:
   Oracle: Elite
   power_builder: Elite
   Full Stack Developer: Lame
education:
   4 GCSEs
   3 A-Levels
   MCA in something called com

現在編寫一個程式碼來通過yaml.load函式載入這個yaml檔案。如下程式碼 -

import yaml

with open('data1.yaml') as f:
    struct = yaml.load(f)

print(struct)
print('======================================')
print("To make the output in more readable format. add the json format.")

import json

print(json.dumps(struct, indent=4, separators=(',', ':')))

由於輸出看起來不那麼可讀,最後通過使用json來美化它。 比較得到的輸出結果和實際yaml檔案。

{'name': 'Raagvendra Joshi', 'job': 'Developer', 'skill': 'Oracle', 'employed': True, 'foods': ['Apple', 'Orange', 'Strawberry', 'Mango'], 'languages': {'Oracle': 'Elite', 'power_builder': 'Elite', 'Full Stack Developer': 'Lame'}, 'education': '4 GCSEs 3 A-Levels MCA in something called com'}
======================================
To make the output in more readable format. add the json format.
{
    "name":"Raagvendra Joshi",
    "job":"Developer",
    "skill":"Oracle",
    "employed":true,
    "foods":[
        "Apple",
        "Orange",
        "Strawberry",
        "Mango"
    ],
    "languages":{
        "Oracle":"Elite",
        "power_builder":"Elite",
        "Full Stack Developer":"Lame"
    },
    "education":"4 GCSEs 3 A-Levels MCA in something called com"
}

軟體開發最重要的一個方面是偵錯。 在本節中,我們將看到使用內建偵錯器或第三方偵錯器進行Python偵錯的不同方法。

PDB - Python偵錯器

模組PDB支援設定斷點。 斷點是程式的故意暫停,您可以在其中獲取有關程式狀態的更多資訊。

要設定斷點,請插入該行 -

pdb.set_trace()

範例程式碼

pdb_example1.py
import pdb
x = 9
y = 7
pdb.set_trace()
total = x + y
pdb.set_trace()

在這個程式中插入了幾個斷點。 程式將在每個斷點處暫停(pdb.set_trace())。 要檢視變數內容,只需鍵入變數名稱即可。

c:\Python\Python361>Python pdb_example1.py
> c:\Python\Python361\pdb_example1.py(8)<module>()
-> total = x + y
(Pdb) x
9
(Pdb) y
7
(Pdb) total
*** NameError: name 'total' is not defined
(Pdb)

c或繼續執行程式直到下一個斷點。

(Pdb) c
--Return--
> c:\Python\Python361\pdb_example1.py(8)<module>()->None
-> total = x + y
(Pdb) total
16

最終,需要偵錯更大的程式 - 使用子程式的程式。 有時候,試圖找到的問題將存在於子程式中。 考慮以下程式。

import pdb
def squar(x, y):
   out_squared = x^2 + y^2
   return out_squared
if __name__ == "__main__":
   #pdb.set_trace()
   print (squar(4, 5))

現在執行上面的程式,

c:\Python\Python361>Python pdb_example2.py
> c:\Python\Python361\pdb_example2.py(10)<module>()
-> print (squar(4, 5))
(Pdb)

可以用問號(?)獲得幫助,但箭頭表示即將執行的行。 在這一點上,按s進入該行。

(Pdb) s
--Call--
>c:\Python\Python361\pdb_example2.py(3)squar()
-> def squar(x, y):

這是對函式的呼叫。 如果想要了解程式碼中的位置,請按l -

(Pdb) l
1 import pdb
2
3 def squar(x, y):
4 -> out_squared = x^2 + y^2
5
6 return out_squared
7
8 if __name__ == "__main__":
9 pdb.set_trace()
10 print (squar(4, 5))
[EOF]
(Pdb)

可以點選n進入下一行。 此時,位於out_squared方法內,並且可以存取函式宣告的變數。即:xy

(Pdb) x
4
(Pdb) y
5
(Pdb) x^2
6
(Pdb) y^2
7
(Pdb) x**2
16
(Pdb) y**2
25
(Pdb)

所以可以看到^運算子不是我們想要的,而是需要使用**運算子來計算正方形。

這樣就可以在函式/方法中偵錯程式。

紀錄檔記錄

自Python 2.3版以來,紀錄檔記錄模組已成為Python標準庫的一部分。 由於它是一個內建模組,所有Python模組都可以參與紀錄檔記錄,因此應用程式紀錄檔可以包含自己的訊息,該訊息與來自第三方模組的訊息整合在一起。 它提供了很多靈活性和功能。

紀錄檔記錄的好處

  • 診斷紀錄檔記錄 - 它記錄與應用程式操作相關的事件。
  • 稽核紀錄檔記錄 - 它記錄業務分析的事件。

訊息以「嚴重性」和最小值的級別寫入和記錄

  • DEBUG (debug()) - 用於開發的診斷訊息。
  • INFO (info()) - 標準的「進度」訊息。
  • WARNING (warning()) - 檢測到非嚴重問題。
  • ERROR (error()) - 遇到錯誤,可能很嚴重。
  • CRITICAL (critical()) - 通常是致命錯誤(程式停止)。

看看下面的簡單程式,

import logging

logging.basicConfig(level=logging.INFO)

logging.debug('this message will be ignored') # This will not print
logging.info('This should be logged') # it'll print
logging.warning('And this, too') # It'll print

上面在嚴重性級別上記錄訊息。首先匯入模組,呼叫basicConfig並設定紀錄檔記錄級別。在上面設定的級別是INFO。 然後有三個不同的語句:debug語句,info語句和warning語句。

上面程式碼的輸出結果 -

INFO:root:This should be logged
WARNING:root:And this, too

由於info語句低於debug語句,我們無法看到偵錯訊息。 要在Output終端中獲取偵錯語句,需要更改的是basicConfig級別。

logging.basicConfig(level = logging.DEBUG)

可以看到,如下所示的輸出 -

DEBUG:root:this message will be ignored
INFO:root:This should be logged
WARNING:root:And this, too

此外,預設行為意味著如果不設定任何紀錄檔記錄級別是警告。只需註釋掉上面程式中的第二行並執行程式碼即可。

#logging.basicConfig(level = logging.DEBUG)

輸出結果如下所示 -

WARNING:root:And this, too

內建紀錄檔記錄級別的Python實際上是整數。

>>> import logging
>>>
>>> logging.DEBUG
10
>>> logging.CRITICAL
50
>>> logging.WARNING
30
>>> logging.INFO
20
>>> logging.ERROR
40
>>>

還可以將紀錄檔訊息儲存到檔案中。如下程式碼指定儲存的檔案 -

logging.basicConfig(level = logging.DEBUG, filename = 'logging.log')

現在所有紀錄檔訊息都將轉到當前工作目錄中的檔案(logging.log)而不是螢幕。 這是一個更好的方法,因為它允許對得到的訊息進行後期分析。

還可以使用紀錄檔訊息設定日期戳。

logging.basicConfig(level=logging.DEBUG, format = '%(asctime)s %(levelname)s:%(message)s')

輸出會得到類似如下結果,

2018-03-08 19:30:00,066 DEBUG:this message will be ignored
2018-03-08 19:30:00,176 INFO:This should be logged
2018-03-08 19:30:00,201 WARNING:And this, too

基準測試

基準測試或分析基本上是為了測試程式碼的執行速度和瓶頸在哪裡? 這樣做的主要原因是為了優化。

timeit
Python附帶了一個名為timeit的內建模組。可以使用它來計算小程式碼片段的時間。 timeit模組使用特定於平台的時間函式,以便可以獲得最準確的時序。

因此,它允許比較每個程式碼的兩個程式碼,然後優化指令碼以獲得更好的效能。

timeit模組具有命令列介面,但也可以匯入。

呼叫指令碼有兩種方法。這裡首先使用如何使用指令碼,執行下面的程式碼並檢視輸出。

import timeit
print ( 'by index: ', timeit.timeit(stmt = "mydict['c']", setup = "mydict = {'a':5, 'b':10, 'c':15}", number = 1000000))
print ( 'by get: ', timeit.timeit(stmt = 'mydict.get("c")', setup = 'mydict = {"a":5, "b":10, "c":15}', number = 1000000))

執行上面範例程式碼,得到以下結果 -

by index: 0.1809192126703489
by get: 0.6088525265034692

上面使用兩種不同的方法。 通過下標並獲取存取字典鍵值。執行語句100萬次,因為它對於非常小的資料執行得太快。 現在,與get相比,可以看到索引存取更快。 可以多次執行程式碼,並且執行時間會略有不同,以便更好地理解。

另一種方法是在命令列中執行上述測試。如下 -

c:\Python\Python361>Python -m timeit -n 1000000 -s "mydict = {'a': 5, 'b':10, 'c':15}" "mydict['c']"
1000000 loops, best of 3: 0.187 usec per loop

c:\Python\Python361>Python -m timeit -n 1000000 -s "mydict = {'a': 5, 'b':10, 'c':15}" "mydict.get('c')"
1000000 loops, best of 3: 0.659 usec per loop

以上輸出可能因系統硬體以及系統中當前執行的所有應用程式而異。

下面我們可以使用timeit模組,如果想呼叫一個函式。 因為可以在函式內部新增多個語句來測試。

import timeit

def testme(this_dict, key):
   return this_dict[key]

print (timeit.timeit("testme(mydict, key)", setup = "from __main__ import testme; mydict = {'a':9, 'b':18, 'c':27}; key = 'c'", number = 1000000))

執行上面範例程式碼,得到以下結果 -

0.7713474590139164