Python 簡易手冊
單元 37 - 二進制序列
二進制序列 (binary sequence) 包括 bytes 、 bytearray 及 memoryview , bytes 是資料儲存在電腦中的原始格式,也就是數字,主要用途是網路傳輸,因為直接用數字傳輸資料更快更方便,所以傳送端先轉換編碼為 bytes ,然後接收端再轉換回所需要的資料型態 (data type) 。 bytes 是不可變的 (immutable) 序列 (sequence) ,不可變的序列支援以下操作
操作 | 說明 |
---|---|
x in s | 判斷 x 是否是 s 的元素 |
x not in s | 判斷 x 是否不是 s 的元素 |
s + t | 回傳合併 s 與 t 的新序列 |
s * n n * s | 回傳將 s 複製 n 倍的新序列 |
s[i] | 取得 s 中索引值為 i 的元素 |
s[i:j] | 取得 s 中索引值為 i 到 j - 1 的子序列 |
s[i:j:k] | 取得 s 中索引值為 i 到 j - 1 間隔 k 的子序列 |
len(s) | 回傳 s 中的元素總量 |
min(s) | 回傳 s 中的最小值 |
max(s) | 回傳 s 中的最大值 |
bytes([source[, encoding[, errors]]]) | 將 source 轉換成 bytes 物件 |
bytearray([source[, encoding[, errors]]]) | 將 source 轉換成 bytearray 物件 |
s.index(x[, i[, j]]) | 回傳 s 中 x 的索引值,或是 x 在 i 到 j 之間的索引值 |
s.count(x) | 回傳 x 在 s 中的數量 |
以下為簡單例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 建立字串變數 a = "測試" print(a) # 將字串變數轉換成 bytes 物件 b = bytes(a, "utf-8") print(b) # 假設經過網路傳輸,以下為接收端 c = b.decode("utf-8", "strict") # 印出解碼結果 print(c) # 檔名: byte_demo.py # 說明: 《Python簡易手冊》的範例 # 網址: http://kaiching.org # 作者: Kaiching Chang # 時間: 2024 年 5 月 |
第 2 行建立字串 (string) 變數 (variable) a
1 2 3 | # 建立字串變數 a = "測試" print(a) |
然後在第 6 行用內建函數 (built-in function) bytes() 將 a 轉換成 bytes 型態的變數 b , bytes() 的第一個引數 (argument) 是要轉換的字串,第二個引數則是編碼格式
5 6 7 | # 將字串變數轉換成 bytes 物件 b = bytes(a, "utf-8") print(b) |
第 10 行,重新將變數 b 解碼回字串
9 10 | # 假設經過網路傳輸,以下為接收端 c = b.decode("utf-8", "strict") |
執行結果如下
> python byte_demo.py |
測試 b'\xe6\xb8\xac\xe8\xa9\xa6' 測試 |
> |
可以看到以上輸出的第 2 行, bytes 型態是 b 開頭的引號,這也是 bytes 型態的字面常數 (literal) ,至於 \x 表示 16 進位數字,所以一個中文字在電腦中的原始格式需要三個 16 進位數字,另外 bytes 的字面常數裡頭也只能用 ASCII 字元,確保 bytes 都是數字。
而 bytearray 是相對 bytes 的可變 (mutable) 序列,增加支援以下操作
操作 | 說明 |
---|---|
s[i] = x | 將 s 中索引值為 i 的元素設定為 x |
s[i:j] = t | 將 s 中索引值 i 到 j - 1 的元素替換成 t 的元素 |
del s[i:j] | 刪除 s 中 i 到 j - 1 的元素 |
s[i:j:k] = t | 將 s 中索引值 i 到 j - 1 間隔 k 的元素替換成 t 的元素 |
del s[i:j:k] | 刪除 s 中索引值 i 到 j - 1 間隔 k 的元素 |
s.append(x) | 將 x 附加到 s 的最後 |
s.clear() | 刪除 s 中的所有元素 |
s.copy() | 回傳 s 的拷貝 |
s.extend(t) s += t | 合併 s 與 t |
s *= n | 將 s 複製 n 倍 |
s.insert(i, x) | 將 x 插入 s 中索引值為 i 的位置 |
s.pop() s.pop(i) | 移除 s 的最後一個元素或是索引值為 i 的元素 |
s.remove(x) | 移除 s 中的第一個 x |
s.reverse() | 反轉 s 中元素的順序 |
舉簡單例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 建立 bytearray 物件 a = bytearray("測試", encoding="utf-8") b = bytes("一下", encoding="utf-8") # 進行連接 a += b # 解碼並印出 c = a.decode("utf-8", "strict") print(c) # 檔名: byte_demo2.py # 說明: 《Python簡易手冊》的範例 # 網址: http://kaiching.org # 作者: Kaiching Chang # 時間: 2024 年 5 月 |
這個例子先建立 bytearray 的變數 a ,再建立 bytes 型態的變數 b ,然後用 += 將 b 附加到 a 的後面,此例執行結果如下
> python byte_demo2.py |
測試一下 |
> |
+= 是算術運算子 (arithmetic operator) 跟指派運算子 (assignment operator) 合在一起用,但是可以作為序列連接,單元 12 - 算術運算子介紹算術運算子,單元 14 - 指派陳述與指派運算式介紹指派運算子。
最後 memoryview 是支援緩衝協議 (buffer protocol) 的序列,特點是可以大幅加速子序列處理速度,舉例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # 從標準程式庫引入 time import time # 設定次數 times = [] for i in range(100000, 500001, 100000): times.append(i) # 計算並印出 bytes 的處理時間 for n in times: data = b't' * n start = time.time() while data: data = data[1:] s = time.time() - start print(f' bytes: {n} {s:0.3f}') # 計算並印出 memoryview 的處理時間 for n in times: data = b'x'*n start = time.time() b = memoryview(data) while b: b = b[1:] s = time.time() - start print(f'memoryview: {n} {s:0.3f}') # 檔名: byte_demo3.py # 說明: 《Python簡易手冊》的範例 # 網址: http://kaiching.org # 作者: Kaiching Chang # 時間: 2024 年 5 月 |
這個例子利用標準程式庫 (standard library) 中的 time 分別計算比較單純 bytes 及用 memoryview 處理子序列的時間,總共計算十萬到五十萬等次數,執行結果如下
> python byte_demo3.py |
bytes: 100000 0.231 bytes: 200000 0.506 bytes: 300000 0.924 bytes: 400000 1.508 bytes: 500000 2.382 memoryview: 100000 0.007 memoryview: 200000 0.015 memoryview: 300000 0.022 memoryview: 400000 0.029 memoryview: 500000 0.037 |
> |
需要稍等一下才跑完程式,由結果可以看出 memoryview 的速度快很多,原因是序列預設取得子序列需要完整拷貝整個序列,而 memoryview 並沒有完整拷貝,所以處理速度快很多。