全網最適合入門的物件導向程式設計教程:55 Python字串與序列化-位元組序列型別和可變位元組字串

FreakStudio發表於2024-10-03

全網最適合入門的物件導向程式設計教程:55 Python 字串與序列化-位元組序列型別和可變位元組字串

image

摘要:

在 Python 中,字元編碼是將字元對映為位元組的過程,而位元組序列(bytes)則是儲存這些位元組的實際資料結構,位元組序列和可變位元組字串的主要區別在於其可變性和用途,bytearray 是可變的位元組序列,允許修改其內容。

原文連結:

FreakStudio的部落格

往期推薦:

學嵌入式的你,還不會物件導向??!

全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論

全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念

全網最適合入門的物件導向程式設計教程:02 類和物件的 Python 實現-使用 Python 建立類

全網最適合入門的物件導向程式設計教程:03 類和物件的 Python 實現-為自定義類新增屬性

全網最適合入門的物件導向程式設計教程:04 類和物件的Python實現-為自定義類新增方法

全網最適合入門的物件導向程式設計教程:05 類和物件的Python實現-PyCharm程式碼標籤

全網最適合入門的物件導向程式設計教程:06 類和物件的Python實現-自定義類的資料封裝

全網最適合入門的物件導向程式設計教程:07 類和物件的Python實現-型別註解

全網最適合入門的物件導向程式設計教程:08 類和物件的Python實現-@property裝飾器

全網最適合入門的物件導向程式設計教程:09 類和物件的Python實現-類之間的關係

全網最適合入門的物件導向程式設計教程:10 類和物件的Python實現-類的繼承和里氏替換原則

全網最適合入門的物件導向程式設計教程:11 類和物件的Python實現-子類呼叫父類方法

全網最適合入門的物件導向程式設計教程:12 類和物件的Python實現-Python使用logging模組輸出程式執行日誌

全網最適合入門的物件導向程式設計教程:13 類和物件的Python實現-視覺化閱讀程式碼神器Sourcetrail的安裝使用

全網最適合入門的物件導向程式設計教程:全網最適合入門的物件導向程式設計教程:14 類和物件的Python實現-類的靜態方法和類方法

全網最適合入門的物件導向程式設計教程:15 類和物件的 Python 實現-__slots__魔法方法

全網最適合入門的物件導向程式設計教程:16 類和物件的Python實現-多型、方法重寫與開閉原則

全網最適合入門的物件導向程式設計教程:17 類和物件的Python實現-鴨子型別與“file-like object“

全網最適合入門的物件導向程式設計教程:18 類和物件的Python實現-多重繼承與PyQtGraph串列埠資料繪製曲線圖

全網最適合入門的物件導向程式設計教程:19 類和物件的 Python 實現-使用 PyCharm 自動生成檔案註釋和函式註釋

全網最適合入門的物件導向程式設計教程:20 類和物件的Python實現-組合關係的實現與CSV檔案儲存

全網最適合入門的物件導向程式設計教程:21 類和物件的Python實現-多檔案的組織:模組module和包package

全網最適合入門的物件導向程式設計教程:22 類和物件的Python實現-異常和語法錯誤

全網最適合入門的物件導向程式設計教程:23 類和物件的Python實現-丟擲異常

全網最適合入門的物件導向程式設計教程:24 類和物件的Python實現-異常的捕獲與處理

全網最適合入門的物件導向程式設計教程:25 類和物件的Python實現-Python判斷輸入資料型別

全網最適合入門的物件導向程式設計教程:26 類和物件的Python實現-上下文管理器和with語句

全網最適合入門的物件導向程式設計教程:27 類和物件的Python實現-Python中異常層級與自定義異常類的實現

全網最適合入門的物件導向程式設計教程:28 類和物件的Python實現-Python程式設計原則、哲學和規範大彙總

全網最適合入門的物件導向程式設計教程:29 類和物件的Python實現-斷言與防禦性程式設計和help函式的使用

全網最適合入門的物件導向程式設計教程:30 Python的內建資料型別-object根類

全網最適合入門的物件導向程式設計教程:31 Python的內建資料型別-物件Object和型別Type

全網最適合入門的物件導向程式設計教程:32 Python的內建資料型別-類Class和例項Instance

全網最適合入門的物件導向程式設計教程:33 Python的內建資料型別-物件Object和型別Type的關係

全網最適合入門的物件導向程式設計教程:34 Python的內建資料型別-Python常用複合資料型別:元組和命名元組

全網最適合入門的物件導向程式設計教程:35 Python的內建資料型別-文件字串和__doc__屬性

全網最適合入門的物件導向程式設計教程:36 Python的內建資料型別-字典

全網最適合入門的物件導向程式設計教程:37 Python常用複合資料型別-列表和列表推導式

全網最適合入門的物件導向程式設計教程:38 Python常用複合資料型別-使用列表實現堆疊、佇列和雙端佇列

全網最適合入門的物件導向程式設計教程:39 Python常用複合資料型別-集合

全網最適合入門的物件導向程式設計教程:40 Python常用複合資料型別-列舉和enum模組的使用

全網最適合入門的物件導向程式設計教程:41 Python常用複合資料型別-佇列(FIFO、LIFO、優先順序佇列、雙端佇列和環形佇列)

全網最適合入門的物件導向程式設計教程:42 Python常用複合資料型別-collections容器資料型別

全網最適合入門的物件導向程式設計教程:43 Python常用複合資料型別-擴充套件內建資料型別

全網最適合入門的物件導向程式設計教程:44 Python內建函式與魔法方法-重寫內建型別的魔法方法

全網最適合入門的物件導向程式設計教程:45 Python實現常見資料結構-連結串列、樹、雜湊表、圖和堆

全網最適合入門的物件導向程式設計教程:46 Python函式方法與介面-函式與事件驅動框架

全網最適合入門的物件導向程式設計教程:47 Python函式方法與介面-回撥函式Callback

全網最適合入門的物件導向程式設計教程:48 Python函式方法與介面-位置引數、預設引數、可變引數和關鍵字引數

全網最適合入門的物件導向程式設計教程:49 Python函式方法與介面-函式與方法的區別和lamda匿名函式

全網最適合入門的物件導向程式設計教程:50 Python函式方法與介面-介面和抽象基類

全網最適合入門的物件導向程式設計教程:51 Python函式方法與介面-使用Zope實現介面

全網最適合入門的物件導向程式設計教程:52 Python函式方法與介面-Protocol協議與介面

全網最適合入門的物件導向程式設計教程:53 Python字串與序列化-字串與字元編碼

全網最適合入門的物件導向程式設計教程:54 Python字串與序列化-字串格式化與format方法

更多精彩內容可看:

給你的 Python 加加速:一文速通 Python 平行計算

一文搞懂 CM3 微控制器除錯原理

肝了半個月,嵌入式技術棧大彙總出爐

電子計算機類比賽的“武林秘籍”

一個MicroPython的開源專案集錦:awesome-micropython,包含各個方面的Micropython工具庫

Avnet ZUBoard 1CG開發板—深度學習新選擇

SenseCraft 部署模型到Grove Vision AI V2影像處理模組

文件和程式碼獲取:

可訪問如下連結進行對文件下載:

https://github.com/leezisheng/Doc

image

本文件主要介紹如何使用 Python 進行物件導向程式設計,需要讀者對 Python 語法和微控制器開發具有基本瞭解。相比其他講解 Python 物件導向程式設計的部落格或書籍而言,本文件更加詳細、側重於嵌入式上位機應用,以上位機和下位機的常見串列埠資料收發、資料處理、動態圖繪製等為應用例項,同時使用 Sourcetrail 程式碼軟體對程式碼進行視覺化閱讀便於讀者理解。

相關示例程式碼獲取連結如下:https://github.com/leezisheng/Python-OOP-Demo

正文

字元編碼和位元組序列 bytes

在計算機記憶體中,我們遵循標準化的編碼原則,普遍採用 Unicode 編碼體系。然而,當需要將資料持久化儲存至硬碟或進行網路傳輸時,我們則將其轉換為 UTF-8 編碼。舉例來說,當我們使用記事本進行文件編輯時,從檔案中讀取的 UTF-8 字元將被轉換為 Unicode 字元以適應記憶體處理。編輯工作完成後,系統再將這些 Unicode 字元轉換回 UTF-8 編碼,以確保檔案儲存的準確性和相容性。這一流程確保了資料在不同平臺和系統間的順暢交流,滿足了現代計算環境中對於資料編碼的多樣化需求。

image

image

image

我們可以透過如下程式碼獲取我們自己計算機預設的編碼方式:

import sys
print(sys.getdefaultencoding())

我們來看一下執行結果,顯示系統預設 UTF-8 編碼:

image

Python3 中明確區分字串型別 (str) 和位元組序列型別 (bytes),也稱為位元組流。記憶體,磁碟中均是以位元組流的形式儲存資料,它由一個一個的位元組(byte,8bit)順序構成。在前文中,我們提到位元組是計算過程中最底層的儲存格式,所以字串實際上為內建的 bytes 型別。然而人們並不習慣直接使用位元組,既讀不懂,操作起來也很麻煩,人們容易看懂的是字串。所以字串和位元組流需要進行轉化,位元組流轉換為人們可以讀懂的過程叫做解碼,與此相反,將字串轉換為位元組流的過程叫做編碼。在下面的示例中,我們先宣告瞭一個 bytes 陣列,然後用 bytes 類的.decode 方法將其轉換為我們可以看得懂的 Unicode 字串。這個方法接受不同字元編碼方式作為引數,示例程式碼如下:

_# 表示 Latin-1 編碼中的 cliché_
_# b 表示在定義一個 bytes 物件_
_# \x 字元表示每個位元組用十六進位制數字表示_
bytestr = b'\x63\x6c\x69\x63\x68\xe9'
_# 輸出這些位元組的 ASCII 符號本身_
_# 對於 ASCII 來說是未知的字元還是保留原有的十六進位制格式_
_# 輸出結果包含了 b 字元,提示我們這是一個bytes,而不是字串_
print(bytestr)
_# 用 Latin-1 編碼來解碼字串,decode 方法返回一個正常字串_
print(bytestr.decode("latin-1"))
_# 使用 iso8859-5 編碼來解碼字串_
print(bytestr.decode("iso8859-5"))
_# 使用 CP437 編碼來解碼字串_
print(bytestr.decode("CP437"))

執行結果如下:

image

我們也可以使用 encode 將字串轉換為 bytes 陣列,示例程式碼如下:

str = "cliché"
_# 前 3 個編碼為重音字元建立了不同的位元組集合_
print(str.encode("UTF-8"))
print(str.encode("latin-1"))
print(str.encode("CP437"))
_# 第 4 個則無法處理這種情況 ,有未知字元_
print(str.encode("ascii"))

執行結果如下:

image

可以看到,最後一種情況丟擲了異常,因為具有未知字元,有時候我們想要讓那些未知字元以不同的方式進行處理。encode 方法還接受一個可選的字串引數,名為 errors,用於定義這個字元應該如何處理。可選的值如下所示:

  • strict:strict 替換規則是預設的,當位元組序列在我們所用的編碼中無法表示某個字元時,會丟擲異常;
  • replace:使用 replace 策略時,這個字元會用另外一個字元替換;
  • ignore:ignore 策略會直接省去不認識的字元;
  • xmlcharrefreplace:xmlcharrefreplace 策略建立一個 xml 實體表示 Unicode 字元,這樣可以根據 XML 文件來轉換字串。

image

image

Python 有大約 100 種不同的編碼格式,詳情可以看如下連結:

https://docs.python.org/zh-cn/3/library/codecs.html#standard-encodings

一些編碼格式有多個名稱,比如'latin-1'、'iso_8859_1'和'8859’都是指同一種編碼。在處理編碼檔案時,選擇正確的編碼至關重要。我強烈推薦使用 UTF-8 編碼。UTF-8 不僅具備表示任何 Unicode 字元的能力,更在現代軟體領域中已成為一種廣泛接受的標準。

Python 原始碼也是一個文字檔案,所以,當你的原始碼中包含中文的時候,在儲存原始碼時,就需要務必指定儲存為 UTF-8 編碼。當 Python 直譯器讀取原始碼時,為了讓它按 UTF-8 編碼讀取,我們通常在檔案開頭寫上這兩行:

_#!/usr/bin/env python3_
_# -*- coding: utf-8 -*-_

第一行註釋是為了告訴 Linux/OS X 系統,這是一個 Python 可執行程式,Windows 系統會忽略這個註釋;

第二行註釋是為了告訴 Python 直譯器,按照 UTF-8 編碼讀取原始碼,否則,你在原始碼中寫的中文輸出可能會有亂碼。

申明瞭 UTF-8 編碼並不意味著你的.py 檔案就是 UTF-8 編碼的,必須並且要確保 IDE 正在使用 UTF-8 編碼。以 PyCharm IDE 為例:

image

可變位元組字串 bytearray

bytes 型別與 str 類似,它們都是不可變的。這意味著一旦建立了一個 bytes 物件,我們就不能對其進行擴充套件或修改。儘管如此,我們仍然可以對 bytes 物件執行索引或切片操作,甚至搜尋特定的位元組序列。然而,在處理 I/O 操作時,這種不可變性可能會帶來一些不便。因為在進行輸入/輸出操作時,我們經常需要快取位元組,直到它們準備好被髮送。

例如,當從套接字接收資料時,可能需要多次呼叫 recv 函式才能接收完整的訊息。在這種情況下,我們可以使用內建的 bytearray 型別來解決這個問題。bytearray 的行為類似於列表,但它包含的元素是位元組。它的建構函式可以接受 bytes 物件作為引數進行初始化。此外,bytearray 還提供了 extend 方法,允許我們向陣列中新增更多的 bytes 物件,例如在從套接字或其他 I/O 通道接收到更多資料時,這種靈活性使得 bytearray 在處理需要動態修改位元組序列的場景中非常有用。

bytearray 用法如下:

Syntax: bytearray(source, encoding, errors)
Parameters:
-source[optional]: Initializes the array of bytes
-encoding[optional]: Encoding of the string
-errors[optional]: Takes action when encoding fails
Returns: 
Returns an array of bytes of the given size.

在下面的示例中,我們建立了兩個 bytearray 陣列,並使用 bytearray() 函式對字串進行編碼:

str = "Geeksforgeeks"

_# encoding the string with unicode 8 and 16_
array1 = bytearray(str, 'utf-8')
array2 = bytearray(str, 'utf-16')

print(array1)
print(array2)

程式碼輸出如下:

image

我們也可以透過切片操作直接修改 bytearray:

array2[4:6] = b"\x15\xa3"
print(array2)

執行結果如下:

image

需要注意的是,如果我們想要操作 bytearray 中的單個元素,則需要傳入一個 0~255 的整數值。這個整數代表的是一個特定的 bytes。如果我們用字元或 bytes 物件,將會丟擲異常。

單位元組字元可以透過 ord 函式(ordinal 的簡寫)轉換成整數。這一函式返回表示一個單獨字元的整數。示例程式碼如下:

b = bytearray(b'abcdef')
b[3] = ord(b'g')
b[4] = 68
print(b)

執行結果如下:

image

這裡,我們在構造完成可變位元組字串後,將索引為 3 的字元(第 4 個字元,因為和列表一樣,索引是從 0 開始的)替換為 103。它是透過 ord 函式返回的 ASCII 字元中的小寫字母 g 所對應的數字。作為說明,我們也將下一個字元替換為位元組數字 68,它對應 ASCII 字元中的大寫字母 D。

bytearray 型別的一些方法讓它可以像列表一樣操作,我們也可以對其進行遍歷、求其長度、使用 count 和 find 等方法,就像是對 bytes 或 str 物件一樣。不同之處在於 bytearray 是可變型別,它可以用於從特定輸入源構建複雜序列。

_# 建立bytearray_
array = bytearray(b"acbcdc")
_# 遍歷_
for value in array:
    print(value)
_# 對元素b'c'使用count方法_
print("Count of c is:", array.count(b"c"))
_# 對元素b'c'使用find方法_
print("Count of c is:", array.find(b"c"))
_# 對元素b'c'使用len方法_
print("Count of bytes:", len(array))

image

相關文章