struct
--- 將位元組直譯為打包起來的二進位資料¶
原始碼:Lib/struct.py
此模組可在 Python 數值與以 Python bytes
物件表示的 C 結構之間進行轉換。簡潔的格式字串描述了與 Python 數值之間預期的轉換。此模組的函式和物件可用於兩種截然不同的應用程式:與外部來源(檔案或網路連線)的資料交換,或是 Python 應用程式與 C 層之間的資料傳輸。
備註
當未給定前綴字元時會預設為原生模式。它會根據建構 Python 直譯器的平台和編譯器來打包或解包資料。打包給定 C 結構的結果包含填充位元組,以維持相關 C 型別的正確對齊;同樣地,解包時也會考量對齊。相反地,當在外部來源之間傳輸資料時,程式設計師需負責定義位元組順序和元素之間的填充。詳情請參見 位元組順序、大小和對齊。
一些 struct
函式(和 Struct
的方法)接受一個 buffer 引數。這是指實作緩衝協定 (Buffer Protocol) 並提供可讀或可讀寫緩衝區的物件。最常用於此目的的型別是 bytes
和 bytearray
,但許多其他可以視為位元組陣列的型別都實作了緩衝區協定,因此它們可以從 bytes
物件讀取或填入資料而無需額外的複製。
函式與例外¶
此模組定義了以下例外和函式:
- exception struct.error¶
在各種情況下都可能觸發的例外;引數是一個描述錯誤內容的字串。
- struct.pack(format, v1, v2, ...)¶
回傳一個包含依照格式字串 format 打包的數值 v1、v2、... 的位元組物件。引數必須完全符合格式所需的數值。
- struct.pack_into(format, buffer, offset, v1, v2, ...)¶
依照格式字串 format 打包數值 v1、v2、...,並將打包後的位元組寫入可寫緩衝區 buffer 中從位置 offset 開始的地方。請注意 offset 是必要的引數。
- struct.unpack(format, buffer)¶
根據格式字串 format 從緩衝區 buffer(推測是由
pack(format, ...)
打包的)解包。結果是一個元組,即使它只包含一個項目。緩衝區的位元組大小必須符合格式所需的大小,如calcsize()
所示。
- struct.unpack_from(format, /, buffer, offset=0)¶
根據格式字串 format 從 buffer 的 offset 位置開始解包。即使它只包含一個項目,結果也都是一個元組。緩衝區從位置 offset 開始的位元組大小,必須至少達到格式所需的大小,如
calcsize()
所示。
- struct.iter_unpack(format, buffer)¶
根據格式字串 format 疊代地從緩衝區 buffer 解包。此函式回傳一個疊代器,它會從緩衝區讀取等大小的區塊,直到消耗完所有內容。緩衝區的位元組大小必須是格式所需大小的倍數,如
calcsize()
所示。每次疊代都會產生 (yield) 出一個由格式字串指定的元組。
在 3.4 版被加入.
- struct.calcsize(format)¶
回傳對應於格式字串 format 的結構大小(因此也是
pack(format, ...)
所產生的位元組物件的大小)。
格式字串¶
格式字串描述了打包和解包資料時的資料配置 (layout)。它們由格式字元組成,這些字元指定了要打包或解包的資料型別。此外,特殊字元控制位元組順序、大小和對齊。每個格式字串由一個可選的前綴字元組成,描述資料的整體屬性,以及一個或多個描述實際資料值和填充的格式字元。
位元組順序、大小和對齊¶
預設情況下,C 型別以機器的原生格式和位元組順序表示,並在必要時透過跳過填充位元組來正確對齊(根據 C 編譯器所使用的規則)。選擇此行為使得打包結構的位元組完全對應於相對應 C 結構的記憶體配置。是否使用原生位元組順序和填充或標準格式取決於應用程式。
或者,格式字串的第一個字元可以用來指示打包資料的位元組順序、大小和對齊,如下表所示:
字元 |
位元組順序 |
大小 |
對齊 |
---|---|---|---|
|
原生 |
原生 |
原生 |
|
原生 |
標準 |
無 |
|
小端序 |
標準 |
無 |
|
大端序 |
標準 |
無 |
|
網路(= 大端序) |
標準 |
無 |
如果第一個字元不是這些字元之一,則假設為 '@'
。
備註
數字 1023(十六進位為 0x3ff
)具有以下位元組表示法:
大端序(
>
)為03 ff
小端序(
<
)為ff 03
Python 範例:
>>> import struct
>>> struct.pack('>h', 1023)
b'\x03\xff'
>>> struct.pack('<h', 1023)
b'\xff\x03'
原生位元組順序是大端序或小端序,會取決於主機系統。例如,Intel x86、AMD64 (x86-64)和 Apple M1 是小端序;IBM z 和許多舊架構是大端序。可使用 sys.byteorder
來檢查你系統的位元組順序。
原生大小和對齊是使用 C 編譯器的 sizeof
運算式來決定的。這總是與原生位元組順序結合使用。
標準大小僅取決於格式字元;請參見格式字元區塊中的表格。
請注意 '@'
和 '='
之間的差異:兩者都使用原生位元組順序,但後者的大小和對齊是標準化的。
'!'
形式表示網路位元組順序,根據 IETF RFC 1700 的定義,它總是大端序。
沒有方法來指示非原生位元組順序(強制位元組交換);請使用適當的 '<'
或 '>'
選擇。
註解:
填充只會在連續的結構成員之間自動加入。編碼結構的開頭或結尾不會加入填充。
使用非原生大小和對齊時,例如使用 '<'、'>'、'=' 和 '!' 時,不會加入填充。
要將結構的結尾對齊到特定型別的對齊需求,請以該型別的碼結束格式,重複次數為零。請參見範例。
格式字元¶
格式字元具有以下意義;在給定型別的情況下,C 和 Python 數值之間的轉換應該是顯而易見的。「標準大小」欄位指的是使用標準大小時(即當格式字串以 '<'
、'>'
、'!'
或 '='
其中之一開始時)打包數值的位元組大小。使用原生大小時,打包數值的大小取決於平台。
格式 |
C Type |
Python 型別 |
標準大小 |
註解 |
---|---|---|---|---|
|
填充位元組 |
無值 |
(7) |
|
|
char |
長度為 1 的位元組 |
1 |
|
|
signed char |
整數 |
1 |
(1), (2) |
|
unsigned char |
整數 |
1 |
(2) |
|
_Bool |
bool |
1 |
(1) |
|
short |
整數 |
2 |
(2) |
|
unsigned short |
整數 |
2 |
(2) |
|
int |
整數 |
4 |
(2) |
|
unsigned int |
整數 |
4 |
(2) |
|
long |
整數 |
4 |
(2) |
|
unsigned long |
整數 |
4 |
(2) |
|
long long |
整數 |
8 |
(2) |
|
unsigned long long |
整數 |
8 |
(2) |
|
|
整數 |
(3) |
|
|
|
整數 |
(3) |
|
|
(6) |
float |
2 |
(4) |
|
float |
float |
4 |
(4) |
|
double |
float |
8 |
(4) |
|
char[] |
位元組 |
(9) |
|
|
char[] |
位元組 |
(8) |
|
|
void* |
整數 |
(5) |
在 3.3 版的變更: 新增 'n'
與 'N'
格式的支援。
在 3.6 版的變更: 新增 'e'
格式的支援。
註解:
'?'
轉換碼對應於自 C99 以來 C 標準定義的 _Bool 型別。在標準模式下,它由一個位元組表示。當嘗試使用任何整數轉換碼打包非整數時,如果非整數具有
__index__()
方法,則會呼叫該方法在打包之前將引數轉換為整數。在 3.2 版的變更: 新增對非整數使用
__index__()
方法的支援。'n'
和'N'
轉換碼僅適用於原生大小(作為預設選擇或使用'@'
位元組順序字元)。對於標準大小,你可以使用適合你應用程式的其他整數格式。對於
'f'
、'd'
和'e'
轉換碼,打包表示法使用 IEEE 754 binary32、binary64 或 binary16 格式(分別對應於'f'
、'd'
或'e'
),無論平台使用何種浮點數格式。'P'
格式字元僅適用於原生位元組順序(作為預設選擇或使用'@'
位元組順序字元)。位元組順序字元'='
根據主機系統選擇使用小端序或大端序。struct 模組不將其解釋為原生順序,因此'P'
格式不可用。IEEE 754 binary16「半精度 (half precision)」型別是在 2008 年的 IEEE 754 標準修訂版中引入的。它有一個符號位元 (sign bit)、5 位元指數和 11 位元精度(明確儲存 10 位元),可以全精度表示大約
6.1e-05
到6.5e+04
之間的數字。此型別未被 C 編譯器廣泛支援:在典型機器上,unsigned short 可以用於儲存但不能用於數學運算。請參見 Wikipedia 上的半精度浮點數格式頁面以取得更多資訊。打包時,
'x'
插入一個 NUL 位元組。'p'
格式字元編碼一個「Pascal 字串」,意思是儲存在固定位元組數中的短、長度可變字串,由計數指定。儲存的第一個位元組是字串的長度,或與 255 間取較小者。字串的位元組在其後。如果傳遞給pack()
的字串太長(長於計數減 1),則只儲存字串的前count-1
個位元組。如果字串短於count-1
,則用空位元組填充,使得總共使用恰好 count 個位元組。請注意,對於unpack()
,'p'
格式字元消耗count
個位元組,但回傳的字串永遠不能包含超過 255 個位元組。對於
's'
格式字元,計數被直譯為位元組的長度,而不是像其他格式字元那樣的重複次數;例如,'10s'
表示一個 10 位元組字串,對應於單一 Python 位元組字串,而'10c'
表示 10 個獨立的單位元組字元元素(例如cccccccccc
),對應於十個不同的 Python 位元組物件。(請參見 範例 以了解具體差異的示範。)如果未給定計數,則預設為 1。打包時,會適當地截斷或用空位元組填充字串以使其適合。解包時,結果位元組物件總是恰好具有指定的位元組數。作為特殊情況,'0s'
表示單一空字串(而'0c'
表示 0 個字元)。
格式字元前面可以加上整數重複次數。例如,格式字串 '4h'
與 'hhhh'
意思完全相同。
格式之間的空白字元會被忽略;但是計數和其格式不能包含空白字元。
當使用整數格式之一('b'
、'B'
、'h'
、'H'
、'i'
、'I'
、'l'
、'L'
、'q'
、'Q'
)打包數值 x
時,如果 x
超出該格式的有效範圍,則會觸發 struct.error
。
在 3.1 版的變更: 以前一些整數格式會環繞超出範圍的數值並引發 DeprecationWarning
而不是 struct.error
。
對於 '?'
格式字元,回傳值是 True
或 False
。打包時,使用引數物件的真值。原生或標準布林值表示法中的 0 或 1 將被打包,解包時任何非零值都將為 True
。
範例¶
備註
原生位元組順序範例(由 '@'
格式前綴或缺少任何前綴字元指定)可能與讀者機器產生的結果不符,因為這取決於平台和編譯器。
使用大端序打包和解包三種不同大小的整數:
>>> from struct import *
>>> pack(">bhl", 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('>bhl', b'\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
>>> calcsize('>bhl')
7
嘗試打包對於定義欄位來說太大的整數:
>>> pack(">h", 99999)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
struct.error: 'h' format requires -32768 <= number <= 32767
示範 's'
和 'c'
格式字元之間的差異:
>>> pack("@ccc", b'1', b'2', b'3')
b'123'
>>> pack("@3s", b'123')
b'123'
解包的欄位可以透過將其指派給變數或將結果包裝在具名元組中來命名:
>>> record = b'raymond \x32\x12\x08\x01\x08'
>>> name, serialnum, school, gradelevel = unpack('<10sHHb', record)
>>> from collections import namedtuple
>>> Student = namedtuple('Student', 'name serialnum school gradelevel')
>>> Student._make(unpack('<10sHHb', record))
Student(name=b'raymond ', serialnum=4658, school=264, gradelevel=8)
在原生模式下,格式字元的順序可能會影響大小,因為填充是隱式的。在標準模式下,使用者負責插入任何所需的填充。請注意下面第一個 pack
呼叫中,在打包的 '#'
之後加入了三個 NUL 位元組,以將後續整數對齊到四位元組邊界。在此範例中,輸出是在小端序機器上產生的:
>>> pack('@ci', b'#', 0x12131415)
b'#\x00\x00\x00\x15\x14\x13\x12'
>>> pack('@ic', 0x12131415, b'#')
b'\x15\x14\x13\x12#'
>>> calcsize('@ci')
8
>>> calcsize('@ic')
5
假設平台的 long 對齊到 4 位元組邊界,以下格式 'llh0l'
會在結尾加入兩個填充位元組:
>>> pack('@llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'
應用¶
struct
模組有兩種主要應用程式:在應用程式內或使用相同編譯器編譯的另一個應用程式之間進行 Python 和 C 程式碼的資料交換(原生格式),以及使用約定資料配置的應用程式之間的資料交換(標準格式)。一般來說,為這兩個領域建構的格式字串是不同的。
原生格式¶
當構造模擬原生配置的格式字串時,編譯器和機器架構會決定位元組順序和填充。在這種情況下,應該使用 @
格式字元來指定原生位元組順序和資料大小。內部填充位元組通常會自動插入。可能需要在格式字串的結尾使用零重複格式碼來向上舍入到正確的位元組邊界,以便正確對齊連續的資料區塊 (chunks)。
考慮這兩個簡單範例(在 64 位元小端序機器上):
>>> calcsize('@lhl')
24
>>> calcsize('@llh')
18
在第二個格式字串的結尾,如果不使用額外填充,資料不會填充到 8 位元組邊界。零重複格式碼解決了這個問題:
>>> calcsize('@llh0l')
24
'x'
格式碼可以用來指定重複,但對於原生格式來說最好使用像 '0l'
這樣的零重複格式。
預設使用原生位元組順序和對齊,但最好明確使用 '@'
前綴字元。
標準格式¶
當與你的行程之外的網路或儲存區等交換資料時,要夠精確。指定確切的位元組順序、大小和對齊。不要假設它們與特定機器的原生順序匹配。例如,網路位元組順序是大端序,而許多流行的 CPU 是小端序。透過明確定義這一點,使用者無需關心其程式碼運行的平台細節。第一個字元通常應該是 <
或 >
(或 !
)。填充是程式設計師的責任。零重複格式字元不會起作用。取而代之的是使用者必須在需要的地方明確加入 'x'
填充位元組。重新檢視上一節的範例,我們有:
>>> calcsize('<qh6xq')
24
>>> pack('<qh6xq', 1, 2, 3) == pack('@lhl', 1, 2, 3)
True
>>> calcsize('@llh')
18
>>> pack('@llh', 1, 2, 3) == pack('<qqh', 1, 2, 3)
True
>>> calcsize('<qqh6x')
24
>>> calcsize('@llh0l')
24
>>> pack('@llh0l', 1, 2, 3) == pack('<qqh6x', 1, 2, 3)
True
上述結果(在 64 位元機器上執行)和在不同機器上執行時不保證會匹配。例如,下面的範例是在 32 位元機器上執行的:
>>> calcsize('<qqh6x')
24
>>> calcsize('@llh0l')
12
>>> pack('@llh0l', 1, 2, 3) == pack('<qqh6x', 1, 2, 3)
False
類別¶
struct
模組也定義了以下型別:
- class struct.Struct(format)¶
回傳一個新的 Struct 物件,它會根據格式字串 format 寫入和讀取二進位資料。建立一次
Struct
物件並呼叫其方法比使用相同格式呼叫模組層級函式更有效率,因為格式字串只編譯一次。備註
傳遞給模組層級函式的最近一個格式字串的編譯版本會被快取起來,因此僅使用少數格式字串的程式無需擔心重複使用單一
Struct
實例。編譯過的 Struct 物件支援以下方法和屬性:
- pack_into(buffer, offset, v1, v2, ...)¶
與
pack_into()
函式相同,會使用編譯過的格式。
- unpack_from(buffer, offset=0)¶
與
unpack_from()
函式相同,會使用編譯過的格式。緩衝區從位置 offset 開始的位元組大小必須至少為size
。
- iter_unpack(buffer)¶
與
iter_unpack()
函式相同,會使用編譯過的格式。緩衝區的位元組大小必須是size
的倍數。在 3.4 版被加入.
- format¶
用於建構此 Struct 物件的格式字串。
在 3.13 版的變更: 結構的 repr() 已經改變。現在是:
>>> Struct('i') Struct('i')