1. 程式人生 > >python檔案處理、路徑處理、序列化和反序列化

python檔案處理、路徑處理、序列化和反序列化

檔案IO常用操作

一般說IO操作,指的是檔案IO,如果指的是網路IO,會直接說。

把檔案儲存到磁碟上的這個過程,叫做落地。

column column
open 開啟
read 讀取
write 寫入
close 關閉
readline 行讀取
readlines 多行讀取
seek 檔案指標操作
tell 指標位置
open開啟操作
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True,opener=None)

f = open("file名字")   #檔案物件
print(f.read())     #讀取檔案
f.close()       #關閉檔案

開啟一個檔案,返回一個檔案物件(流物件)和檔案描述符。開啟檔案失敗,則返回異常。
基本使用: 建立一個檔案test,然後開啟它,用完關閉。

檔案操作中最常用的就是讀和寫。檔案訪問的模式有兩種:文字模式和二進位制模式。

注:windows中使用codepage內碼表。可以認為每一個內碼表就是一張編碼表 cp936和gbk等價。

mode模式
描述字元
r 預設的,表示只讀開啟
w 只寫開啟,有的話就清除重新寫
x 建立並寫入一個新檔案
a 寫入開啟,如果檔案存在,則追加
b 二進位制模式
t 預設的,文字模式
+ 讀寫開啟一個檔案,給原來只讀、只寫的增加缺失的功能

open預設是隻讀模式r開啟已經存在的檔案。

  • r 只讀開啟檔案,如果使用write方法,會拋異常。 如果檔案不存在,丟擲FileNotFoundError異常。

  • w 表示只寫方式開啟,如果讀取則丟擲異常 如果檔案不存在,則直接建立檔案 如果檔案存在,則清空檔案內容。

  • x 檔案不存在,建立檔案,並只寫方式開啟,檔案存在,丟擲FileExistsError異常。

  • a 檔案存在,只寫開啟,追加內容 檔案不存在,則建立後,只寫開啟,追加內容

  • r是隻讀,wxa都是隻寫。 wxa都可以產生新檔案,w不管檔案存在與否,都會生成全新內容的檔案;a不管檔案是否存在,都能在開啟的檔案尾部追加;x必須要求檔案事先不存在,自己造一個新檔案。

  • +為r、w、a、x提供缺失的讀或寫功能,但是,獲取檔案物件依舊按照r、w、a、x自己的特徵。 +不能單獨使用,可以認為它是為前面的模式字元做增強功能的。

t和b:

  • 文字模式t 字元流,將檔案的位元組按照某種字元編碼理解,按照字元操作。open的預設mode就是rt。

  • 二進位制模式b 位元組流,將檔案就按照位元組理解,與字元編碼無關。二進位制模式操作時,位元組操作使用bytes型別。

t/b不能單獨存在,要和a/w/x/r配合使用。

seek檔案指標

檔案指標,指向當前位元組位置。

mode = r,指標起始在0 ,mode = a 指標起始在EOF。

tell():顯示指標當前位置。

seek(offset[, whence]) 移動檔案指標位置。offest偏移多少位元組,whence從哪裡開始。

  • 文字模式下 whence 0 預設值,表示從頭開始,offest只能正整數 whence 1 表示從當前位置,offest只接受0,whence 2 表示從EOF開始,offest只接受0

    # 文字模式
    f = open('test4','r+')
    f.tell() # 起始
    f.read()
    f.tell() # EOF
    f.seek(0) # 起始
    f.read()
    f.seek(2,0)
    f.read()
    f.seek(2,0)
    f.seek(2,1) # offset必須為0
    f.seek(2,2) # offset必須為0
    f.close()
    
  • 二進位制模式下 whence 0 預設值,表示從頭開始,offest只能正整數 whence 1表示從當前位置,offest可正可負,whence 2 表示從EOF開始,offest可正可負。

    # 二進位制模式
    f = open('test4','rb+')
    f.tell() # 起始
    f.read()
    f.tell() # EOF
    f.write(b'abc')
    f.seek(0) # 起始
    f.seek(2,1) # 從當前指標開始,向後2
    f.read()
     
    f.seek(-2,2) # 從EOF開始,向前2
    f.read()
    f.seek(-20,2) # OSError
    f.close()
    
  • 二進位制模式支援任意起點的偏移,從頭、從尾、從中間位置開始。 向後seek可以超界,但是向前seek的時候,不能超界,否則拋異常。

buffering緩衝區

-1 表示使用預設大小的buffer。如果是二進位制模式,使io.DEFAULT_BUFFER_SIZE值,預設是<font color= red>4096或者8192</font>。如果是文字模式,如果是終端裝置,是行快取方式,如果不是,則使用二進位制模式的策略。

  • 0 只在二進位制模式使用,表示關buffer
  • 1 只在文字模式使用,表示使用行緩衝。意思就是見到換行符就flush
  • 大於1 用於指定buffer的大小

buffer 緩衝區

緩衝區一個記憶體空間,一般來說是一個FIFO佇列,到緩衝區滿了或者達到閾值,資料才會flush到磁碟。

flush() 將緩衝區資料寫入磁碟 close() 關閉前會呼叫flush()。

io.DEFAULT_BUFFER_SIZE 預設緩衝區大小,位元組。

buffering 說明
buffering = -1 t和b,都是io.DEFAULT_BUFFER_SIZE
buffering = 0 b 關閉緩衝區
t 不支援
buffering = 1 b 就一個位元組
t 行緩衝,遇到換行符才flush
buffering > 1 b模式表示行緩衝大小。緩衝區的值可以超過io.DEFAULT_BUFFER_SIZE,直到設定的值超出後才把緩衝區flush 。
t模式,是io.DEFAULT_BUFFER_SIZE位元組,flush完後把當前字串也寫入磁碟

一般來說:

  1. 文字模式,一般都用預設緩衝區大小
  2. 二進位制模式,是一個個位元組的操作,可以指定buffer的大小
  3. 一般來說,預設緩衝區大小是個比較好的選擇,除非明確知道,否則不調整它
  4. 一般程式設計中,明確知道需要寫磁碟了,都會手動呼叫一次flush,而不是等到自動flush或者close的時候
其他引數

編碼:windows下預設GBK(0xB0A1),Linux下預設UTF-8(0xE5 95 8A)

errors :編碼錯誤將被捕獲 None和strict表示有編碼錯誤將丟擲ValueError異常;ignore表示忽略

newline:文字模式中,換行的轉換。可以為None、'' 空串、'\r'、'\n'、'\r\n' 。

  • None表示'\r'、'\n'、'\r\n'都被轉換為'\n';
  • '' 表示不會自動轉換通用換行符;其它合法字元表示換行符就是指定字元,就會按照指定字元分行寫。
  • '\n'或''表示'\n'不替換;其它合法字元表示'\n'會被替換為指定的字元

closefd:關閉檔案描述符,True表示關閉它。False會在檔案關閉後保持這個描述符。fileobj.fileno()檢視。

檔案描述符:Linux一切皆檔案,檔案開啟後都會有一個位於的檔案描述符,在計算機系統中是一個有限的資源。0,1,2,標準輸入,標準輸出,標準錯誤輸出。

對於類似於檔案物件的IO物件,一般來說都需要在不使用的時候關閉、登出,以釋放資源。
IO被開啟的時候,會獲得一個檔案描述符。計算機資源是有限的,所以作業系統都會做限制。就是為了保護計算機的資源不要被完全耗盡,計算資源是共享的,不是獨佔的。

一般情況下,除非特別明確的知道資源情況,否則不要提高資源的限制值來解決問題。

read()

read(size=-1)
size表示讀取的多少個字元或位元組;負數或者None表示讀取到EOF

readline(size=-1)
一行行讀取檔案內容。size設定一次能讀取行內幾個字元或位元組。

readlines(hint=-1)
讀取所有行的列表。指定hint則返回指定的行數。

write()

write(s),把字串s寫入到檔案中並返回字元的個數 writelines(lines),將字串列表寫入檔案。

close()

flush並關閉檔案物件。
檔案已經關閉,再次關閉沒有任何效果。

其他
名稱 說明
seekable() 是否可seek
readable() 是否可讀
writeable() 是否可寫
closed() 是否已經關閉
上下文管理

1、異常處理
當出現異常的時候,攔截異常。但是,因為很多程式碼都可能出現OSError異常,還不好判斷異常就是應為資源限制產生的。

f = open('test')
try:
    f.write("abc") # 檔案只讀,寫入失敗
finally:
    f.close() # 這樣才行

使用finally可以保證開啟的檔案可以被關閉。

上下文管理

  1. 使用with ... as 關鍵字

  2. 上下文管理的語句塊並不會開啟新的作用域

  3. with語句塊執行完的時候,會自動關閉檔案物件

StringIO操作

io模組中的類

from io import StringIO

記憶體中,開闢的一個文字模式的buffer,可以像檔案物件一樣操作它

當close方法被呼叫的時候,這個buffer會被釋放

from io import StringIO
# 記憶體中構建
sio = StringIO() # 像檔案物件一樣操作
print(sio.readable(), sio.writable(), sio.seekable())# True True True
sio.write("magedu\nPython")
sio.seek(0)  
print(sio.readline())  #magedu
print(sio.getvalue()) # 無視指標,輸出全部內容  magedu   Python
sio.close()

好處

一般來說,磁碟的操作比記憶體的操作要慢得多,記憶體足夠的情況下,一般的思路是少落地,減少磁碟IO的過程,可以大大的提高程式的執行效率。

BytesIO操作

io模組中的類

from io import BytesIO

記憶體中,開闢的一個二進位制模式的buffer,可以像檔案物件一樣操作它

當close方法被呼叫的時候,這個buffer會被釋放

from io import BytesIO # 記憶體中構建
bio = BytesIO()
print(bio.readable(), bio.writable(), bio.seekable()) #True True True
bio.write(b"magedu\nPython")
bio.seek(0)
print(bio.readline())  # b'magedu\n'
print(bio.getvalue()) # 無視指標,輸出全部內容   b'magedu\nPython'
bio.close()

file-like物件

類檔案物件,可以像檔案物件一樣操作。

socket物件,輸入輸出物件(stdin、stdout)都是類檔案物件

from sys import stdout, stderr
f = stdout
print(type(f))  #<class 'ipykernel.iostream.OutStream'>
f.write('magedu.com')  #magedu.com

路徑操作

os.path模組

3.4版本之前

from os import path
p = path.join('d:/','tmp')
print(type(p), p)               #<class 'str'> d:/tmp
print(path.exists(p))     #判斷是否存在該路徑  True
print(path.split(p)) # (head,tail)     ('d:/', 'tmp')
print(path.abspath('.'))   # 列印當前的絕對路徑      C:\Users\vampire\python
p = path.join('D:/', p, 'test.txt')   #  'd:/tmp\\test.txt'
print(path.dirname(p)) # 目錄名
print(path.basename(p)) #基名,就是檔名
print(path.splitdrive(p)) #二元組  ('d:', '/tmp\\test.txt')

p1 = path.abspath(".")  #“檔案路徑”
print(p1, path.basename(p1))
while p1 != path.dirname(p1):
    p1 = path.dirname(p1)
    print(p1, path.basename(p1))

​```
C:\Users\vampire\python python
C:\Users\vampire vampire
C:\Users Users
C:\ 
​```
pathlib模組

提供Path物件來操作。包括目錄和檔案。

匯入模組:from pathlib import Path

目錄操作初始化

p = Path() # 當前目錄    WindowsPath('.')
p.absolute()#   WindowsPath('C:/Users/vampire/python')
p = Path('a','b','c/d') # 當前目錄下的 WindowsPath('C:/Users/vampire/python/a/b/c/d')
p = Path('/etc') # 根下的etc目錄

路徑拼接和分解

操作符/

Path物件 / Path物件
Path物件 / 字串 或者 字串 / Path物件

分解

parts屬性,可以返回路徑中的每一個部分

p3.absolute()   #WindowsPath('C:/Users/vampire/python/c/a')
p3.absolute().parts   #('C:\\', 'Users', 'vampire', 'python', 'c', 'a')
joinpath

joinpath(*other) 連線多個字串到Path物件中

p = Path()   #   WindowsPath('.')
p = p / 'a'   #  WindowsPath('a')
p.absolute()  #   WindowsPath('C:/Users/vampire/python/a')
p1 = 'b' / p  #   WindowsPath('C:/Users/vampire/python/b/a')
p2 = Path('c') #  WindowsPath('C:/Users/vampire/python/c')
p2.absolute()  #   WindowsPath('C:/Users/vampire/python/c')
p3 = p2 / p1  #  WindowsPath('c/b/a')
p3.absolute()  #  WindowsPath('C:/Users/vampire/python/c/b/a')
print(p3.parts)   #
p3.absolute().parts # ('C:\\', 'Users', 'vampire', 'python', 'c', 'b', 'a')
p3.joinpath('etc','init.d',Path('httpd'))
獲取路徑

str 獲取路徑字串

bytes 獲取路徑字串的bytes

p = Path('/etc')
print(str(p), bytes(p))

#  \etc   b'\\etc'
父目錄

parent 目錄的邏輯父目錄

parents 父目錄序列,索引0是直接的父

p = Path('/a/b/c/d')
print(p.absolute())   #C:\a\b\c\d
print(p.parent.parent)  #\a\b
for x in p.parents:
    print(x)
    
#\a\b\c
#\a\b
#\a
#\
目錄的組合部分

name、stem、suffix、suffixes、with_suffix(suffix)、with_name(name)

name 目錄的最後一個部分

suffix 目錄中最後一個部分的副檔名

stem 目錄最後一個部分,沒有後綴

suffixes 返回多個副檔名列表

with_suffix(suffix) 有副檔名則替換,無則補充副檔名

with_name(name) 替換目錄最後一個部分並返回一個新的路徑

p = Path('mysqlinstall/mysql.tar.gz')
print(p.name)   #mysql.tar.gz
print(p.suffix)  #.gz
print(p.suffixes)  # ['.tar', '.gz']
print(p.stem)   # mysql.tar
print(p.with_name('mysql-5.tgz'))   #\mysqlinstall\mysql-5.tgz
print(p.with_suffix('.png'))   #\mysqlinstall\mysql.tar.png
p = Path('README')  #  README
print(p.with_suffix('.txt'))   #  README.txt
判斷方法

is_dir() 是否是目錄,目錄存在返回True

is_file() 是否是普通檔案,檔案存在返回True

is_symlink() 是否是軟連結

is_socket()是否是socket檔案

is_block_device()是否是塊裝置

is_char_device() 是否是字元裝置

is_absolute()是否是絕對路徑

resolve()返回一個新的路徑,這個新路徑就是當前Path物件的絕對路徑,如果是軟連結則直接被解析

absolute() 獲取絕對路徑

exists()目錄或檔案是否存在

rmdir()刪除空目錄。沒有提供判斷目錄為空的方法

touch(mode=0o666, exist_ok=True) 建立一個檔案

as_uri() 將路徑返回成URI,例如'file:///etc/passwd'

mkdir(mode=0o777, parents=False, exist_ok=False)

  • parents,是否建立父目錄,True等同於mkdir -p;False時,父目錄不存在,則丟擲FileNotFoundError

  • exist_ok引數,在3.5版本加入。False時,路徑存在,丟擲FileExistsError;True時,FileExistsError被忽略

iterdir()迭代當前目錄

匹配

match(pattern)

模式匹配,成功返回True。

Path('a/b.py').match('*.py') # True
Path('/a/b/c.py').match('b/*.py') # True
Path('/a/b/c.py').match('a/*.py') # False
Path('/a/b/c.py').match('a/*/*.py') # True
Path('/a/b/c.py').match('a/**/*.py') # True
Path('/a/b/c.py').match('**/*.py') # True

stat() 相當於stat命令 ,lstat() 同stat(),但如果是符號連結,則顯示符號連結本身的檔案資訊

pathlib模組下的檔案操作

Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)

使用的方法類似內建函式open,返回一個檔案物件。

3.5增加的新函式

Path.read_bytes()

以'rb'讀取路徑對應檔案,並返回二進位制流。看原始碼

Path.read_text(encoding=None, errors=None)

以'rt'方式讀取路徑對應檔案,返回文字。

Path.write_bytes(data)

以'wb'方式寫入資料到路徑對應檔案。

Path.write_text(data, encoding=None, errors=None)

以'wt'方式寫入字串到路徑對應檔案。

p = Path('my_binary_file')
p.write_bytes(b'Binary file contents')
p.read_bytes() # b'Binary file contents'
 
p = Path('my_text_file')
p.write_text('Text file contents')
p.read_text() # 'Text file contents'
 
 
from pathlib import Path
p = Path('o:/test.py')
p.write_text('hello python')
print(p.read_text())
with p.open() as f:
    print(f.read(5))

csv檔案

逗號分隔值Comma-Separated Values。

CSV 是一個被行分隔符、列分隔符劃分成行和列的文字檔案。

CSV 不指定字元編碼。

行分隔符為\r\n,最後一行可以沒有換行符

列分隔符常為逗號或者製表符。

每一行稱為一條記錄record

欄位可以使用雙引號括起來,也可以不使用。如果欄位中出現了雙引號、逗號、換行符必須使用雙引號括起來。如果欄位的值是雙引號,使用兩個雙引號表示一個轉義。

表頭可選,和欄位列對齊就行了。

手動生成csv檔案

from pathlib import Path

p = Path('D:/tmp/test.csv')
parent = p.parent
if not parent.exists():
    parent.mkdir(parents=True,exist_ok =True) #exist_ok 用在python3.5之後,如果檔案目錄存在,True則壓制異常。
csv_body = '''\
id,name,age,comment
1,zs,18,"I'm 18"
2,ls,20,"this is a ""test"" string."
3,ww,23,"你好

計算機
"
'''
p.write_text(csv_body)

csv模組
def reader(iterable, dialect='excel', *args, **kwargs)

返回一個reader物件,是一個行迭代器

預設使用excel方言,如下:

  • delimiter 列分隔符,逗號

  • lineterminator 行分隔符\r\n

  • quotechar 欄位的引用符號,預設為"雙引號

  • 雙引號的處理

    • doublequote 雙引號的處理,預設為True。如果碰到資料中有雙引號,而quotechar也是雙引號,True則使用2個雙引號表示,False表示使用轉義字元將作為雙引號的字首。
    • escapechar 一個轉義字元,預設為None
    • writer = csv.writer(f, doublequote=False, escapechar='@') 遇到雙引號,則必須提供轉義字元
  • quoting 指定雙引號的規則

    • QUOTE_ALL 所有欄位

    • QUOTE_MINIMAL特殊字元欄位,Excel方言使用該規則

    • QUOTE_NONNUMERIC非數字欄位

    • QUOTE_NONE都不使用引號。

def writer(fileobj, dialect='excel', *args, **kwargs)

返回DictWriter例項,主要的方法有writerow,writerows。

import csv
 
p = Path('d://tmp/tesr.csv')
with open(str(p)) as f:
    reader = csv.reader(f)    #返回一個迭代物件
    print(next(reader))     #不回頭
    print(next(reader))     
    for line in reader:
        print(line)
 
rows = [
    [4,'tom',22,'tom'],
    (5,'jerry',24,'jerry'),
    (6,'justin',22,'just\t"in'),
    "abcdefghi",
    ((1,),(2,))
]
row = rows[0]
 
with open(str(p), 'a',newline="") as f:  #newline為了不換行
    writer = csv.writer(f)
    writer.writerow(row)   #一次寫一條
    writer.writerows(rows)    #將所有的一次寫入

ini檔案

一般作為配置檔案。

ini檔案:

[DEFAULT]
a = test
 
[mysql]
default-character-set=utf8
 
[mysqld]
datadir =/dbserver/data
port = 33060
character-set-server=utf8
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

中括號裡面的部分稱為section,譯作節、區、段。

每一個section內,都是key=value形成的鍵值對,key稱為option選項。

這裡的DEFAULT是預設section的名字,必須大寫。

configparser模組

configparser模組的ConfigParser類就是用來操作。

可以將section當做key,section儲存著鍵值對組成的字典,可以把ini配置檔案當做一個巢狀的字典。預設使用的是有序字典。

read(filenames, encoding=None) 
#讀取ini檔案,可以是單個檔案,也可以是檔案列表。可以指定檔案編碼。
sections()  #返回section列表。預設section不包括在內。 
add_section(section_name)   #增加一個section。 

has_section(section_name)   #判斷section是否存在 
options(section)   #返回section的所有option,會追加預設section的option 

has_option(section, option)  #判斷section是否存在這個option
get(section, option, *, raw=False, vars=None[, fallback]) 

#從指定的段的選項上取值,如果找到返回,如果沒有找到就去找DEFAULT段有沒有。

getint(section, option, *, raw=False, vars=None[, fallback]) 
getfloat(section, option, *, raw=False, vars=None[, fallback]) 
getboolean(section, option, *, raw=False, vars=None[, fallback]) 
#上面3個方法和get一樣,返回指定型別資料。

items(raw=False, vars=None) 
items(section, raw=False, vars=None) 
#沒有section,則返回所有section名字及其物件;如果指定section,則返回這個指定的section的鍵值對組成二元組。

set(section, option, value) 
#section存在的情況下,寫入option=value,要求option、value必須是字串。

remove_section(section) 
#移除section及其所有option 

remove_option(section, option) 
#移除section下的option。

write(fileobject, space_around_delimiters=True) 
#將當前config的所有內容寫入fileobject中,一般open函式使用w模式。

程式碼示例:

from configparser import ConfigParser
from pathlib import Path

filename = Path("d://tmp/mysql.ini")
newfilename = Path("d://tmp/mysql111.ini")

cfg = ConfigParser()
read_ok = cfg.read(str(filename))
print(read_ok)
print(cfg.sections())
print(cfg.has_section("mysql"))
print("-"*30)

for k,v in cfg.items():   #未指定section
    print(k,type(k))
    print(v,type(v))
    print(cfg.items(k))
    print("~~~~~~~~~~~~~~~~~~")
print("-"*30)


for k,v in cfg.items("mysqld"):  #指定section
    print(k,type(k))
    print(v,type(v))
    print("~~~~~~~~~~")

tmp = cfg.get("mysqld","port")
print(tmp, type(tmp))
print(cfg.get("mysqld", "a"))
print(cfg.get("mysqld", "python" , fallback= "linux"))  #按照型別,fallbac:給與預設值

tmp = cfg.getint("mysqld", "port")
print(type(tmp), tmp)

cfg.add_section("test")
cfg.set("test","test1","1")
cfg.set("test","test2","2")
with open(newfilename,"w+",newline="") as f:
    cfg.write(f)

print(cfg.getint("test" , "test1"))

cfg.remove_option("test", "test1")
# cfg.remove_section("test")
# print("x" in cfg["test2"])
#字典操作
cfg["test3"] = {"c":"1000"}   #沒有落地,在記憶體中修改

print("x" in cfg["test"])
print("c" in cfg["test3"])

# 其他內部方式
print(cfg._dict)  # 返回預設的字典型別,預設使用有序字典

for k, v in cfg._sections.items():
    print(k, v)

for k,v in cfg._sections['mysqld'].items():
    print(k,v)
#重新寫入檔案
with open(newfilename, 'w') as f:
    cfg.write(f)

序列化和反序列化

要設計一套協議,按照某種規則,把記憶體中資料儲存到檔案中,檔案是一個位元組序列,所以必須把資料轉換成<font color=red >位元組</font>序列,輸出到檔案。這就是序列化。反之,從檔案的位元組序列恢復到記憶體。就是反序列化。
  1. serialization:序列化
    將記憶體中物件儲存下來,變成一個個位元組 --> 二進位制

  2. deseiralization:反序列化
    將檔案中的一個個位元組恢復成記憶體中物件 <--二進位制

序列化儲存到檔案就是持久化,可以將資料序列化後持久化,或者網路傳輸;也可以將檔案中或者網路中接收到的位元組序列反序列化。

pickle庫

python中的序列化,反序列化模組。

dumps 物件序列化為bytes物件 dump 物件序列化到檔案物件,就是存入檔案

loads 從bytes物件反序列化 load 物件反序列化,從檔案讀取資料

序列化的應用

一般來說,本地序列化的情況,應用較少。大多數場景都應用在網路傳輸中。

將資料序列化後通過網路傳輸到遠端節點,遠端伺服器上的服務將接收到的資料反序列化後,就可以使用了。

但是,要注意一點,遠端接收端,反序列化時必須有對應的資料型別,否則就會報錯。尤其是自定義類,必須遠端的有一致的定義。

現在,大多數專案,都不是單機的,也不是單服務的。需要通過網路將資料傳送到其他節點上去,這就需要大量的序列化、反序列化過程。

但是,問題是,Python程式之間還可以都是用pickle解決序列化、反序列化,如果是跨平臺、跨語言、跨協議pickle就不太適合了,就需要公共的協議。例如XML、Json、Protocol Buffer等。

不同的協議,效率不同、學習曲線不同,適用不同場景,要根據不同的情況分析選型。

Json
Json(JavaScript Object Notation,JS物件標記)是一種輕量級的資料交換格式。它基於 ECMAScript (w3c組織制定的JS規範)的一個子集,採用完全獨立於程式語言的文字格式來儲存和表示資料。網址: http://json.org/

Json的資料型別

值:雙引號引起來的字串,數值,true和false,null,物件,陣列,這些都是值

字串:有正負,有整數,浮點數。

物件:無序的鍵值對的集合,格式{key:value...},key必須是一個字串,需要雙引號包圍這個字串,value可以是任意合法的值。

陣列:有序的值的集合 格式[val1,,,,valn]

{
  "person": [
    {
      "name": "tom",
      "age": 18
    },
    {
      "name": "jerry",
      "age": 16
    }
  ],
  "total": 2
}

Json模組

Python支援少量內建資料型別到Json型別的轉換

Python型別 Json型別
True true
False false
None null
str string
int integer
float float
list array
dict object

常用方法

Python型別 Json型別
dumps Json編碼
dump Json編碼並存入檔案
loads Json解碼
load Json解碼,從檔案讀取資料

一般Json編碼的資料很少落地,資料都是通過網路傳輸,傳輸的時候,要考慮壓縮它,節省流量。本質來說它就是個文字,就是個字串。

MessagePack

MessagePack是一個基於二進位制高效的物件序列化類庫,可用於跨語言通訊。

它可以像JSON那樣,在許多種語言之間交換結構物件。

相容 json和pickle。

MessagePack簡單易用,高效壓縮,支援語言豐富。

所以,用它序列化也是一種很好的選擇。

安裝:$pip install msgpack-python

常用方法:

packb 序列化物件。提供了dumps來相容pickle和json。

unpackb 反序列化物件。提供了loads來相容。

pack 序列化物件儲存到檔案物件。提供了dump來相容。

unpack 反序列化物件儲存到檔案物件。提供了load來相容。

import pickle
import json
import msgpack

d = {"person":[{"name":"tom","age":18},{"name":"jerry","age":16}],"total":2}
j = json.dumps(d)
print(j, type(j), len(j)) # 請注意引號的變化
print(len(j.replace(' ',''))) # 72 bytes 注意這樣替換的壓縮是不對的
print("-"*30)
p = pickle.dumps(d)
print(p)
print(len(p))  # 101 bytes
print("-"*30)
m = msgpack.dumps(d)
print(m)
print(len(m)) # 48 bytes
print("-"*30)
u = msgpack.unpackb(m)
print(type(u), u)
u = msgpack.loads(m, encoding='utf-8')
print(type(u), u)
{"person": [{"name": "tom", "age": 18}, {"name": "jerry", "age": 16}], "total": 2} <class 'str'> 82
72
------------------------------
b'\x80\x03}q\x00(X\x06\x00\x00\x00personq\x01]q\x02(}q\x03(X\x04\x00\x00\x00nameq\x04X\x03\x00\x00\x00tomq\x05X\x03\x00\x00\x00ageq\x06K\x12u}q\x07(h\x04X\x05\x00\x00\x00jerryq\x08h\x06K\x10ueX\x05\x00\x00\x00totalq\tK\x02u.'
101
------------------------------
b'\x82\xa6person\x92\x82\xa4name\xa3tom\xa3age\x12\x82\xa4name\xa5jerry\xa3age\x10\xa5total\x02'
48
------------------------------
<class 'dict'> {b'person': [{b'name': b'tom', b'age': 18}, {b'name': b'jerry', b'age': 16}], b'total': 2}
<class 'dict'> {'person': [{'name': 'tom', 'age': 18}, {'name': 'jerry', 'age': 16}], 'total': 2}