struct — 將位元組解釋為打包的二進位制資料

原始碼: Lib/struct.py


此模組在 Python 值和表示為 Python bytes 物件的 C 結構之間進行轉換。緊湊的格式字串描述了到/從 Python 值的預期轉換。該模組的函式和物件可用於兩個截然不同的應用程式:與外部源(檔案或網路連線)進行資料交換,或者在 Python 應用程式和 C 層之間進行資料傳輸。

注意

當未給出字首字元時,預設使用本機模式。它根據構建 Python 直譯器的平臺和編譯器打包或解包資料。打包給定 C 結構的結果包括用於維護所涉及 C 型別正確對齊的填充位元組;類似地,解包時會考慮對齊。相反,當在外部源之間傳輸資料時,程式設計師負責定義位元組順序和元素之間的填充。有關詳細資訊,請參閱位元組順序、大小和對齊

多個 struct 函式(和 Struct 的方法)採用 buffer 引數。這指的是實現緩衝區協議並提供可讀或可讀寫緩衝區的物件。用於此目的的最常見型別是 bytesbytearray,但許多其他可以被視為位元組陣列的型別都實現了緩衝區協議,因此可以讀取/填充它們,而無需從 bytes 物件進行額外複製。

函式和異常

該模組定義了以下異常和函式

exception struct.error

在各種情況下引發的異常;引數是一個描述錯誤的字串。

struct.pack(format, v1, v2, ...)

返回一個 bytes 物件,其中包含根據格式字串 format 打包的值 v1v2、… 。引數必須與格式要求的值完全匹配。

struct.pack_into(format, buffer, offset, v1, v2, ...)

根據格式字串 format 打包值 v1v2、…,並將打包的位元組寫入可寫緩衝區 buffer,從位置 offset 開始。請注意,offset 是一個必需引數。

struct.unpack(format, buffer)

根據格式字串 format 從緩衝區 buffer(大概由 pack(format, ...) 打包)解包。結果是一個元組,即使它只包含一個項。緩衝區的位元組大小必須與格式要求的大小相匹配,如 calcsize() 反映的那樣。

struct.unpack_from(format, /, buffer, offset=0)

根據格式字串 formatbuffer 中從位置 offset 開始解包。結果是一個元組,即使它只包含一個項。從位置 offset 開始,緩衝區的位元組大小必須至少為格式要求的大小,如 calcsize() 反映的那樣。

struct.iter_unpack(format, buffer)

根據格式字串 format 從緩衝區 buffer 迭代解包。此函式返回一個迭代器,它將從緩衝區讀取大小相等的塊,直到所有內容都被使用。緩衝區的位元組大小必須是格式要求大小的倍數,如 calcsize() 反映的那樣。

每次迭代都會生成一個由格式字串指定的元組。

在 3.4 版本中新增。

struct.calcsize(format)

返回與格式字串 format 對應的結構的大小(以及由此產生的 pack(format, ...) 生成的位元組物件的大小)。

格式字串

格式字串描述打包和解包資料時的資料佈局。它們由格式字元組成,這些字元指定正在打包/解包的資料型別。此外,特殊字元控制位元組順序、大小和對齊。每個格式字串都由一個可選的字首字元組成,該字元描述資料的整體屬性以及一個或多個格式字元,這些字元描述實際的資料值和填充。

位元組順序、大小和對齊

預設情況下,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 的定義,它始終是大端位元組序。

沒有辦法指示非本地位元組順序(強制位元組交換);請使用適當的 '<''>'

備註

  1. 僅在連續的結構成員之間自動新增填充。在編碼結構的開頭或結尾處不會新增填充。

  2. 當使用非本地大小和對齊方式時,例如使用 ' < '、' > '、' = ' 和 ' ! ' 時,不會新增填充。

  3. 要將結構的末尾與特定型別的對齊要求對齊,請以該型別的程式碼結束格式,並使用零的重複計數。請參閱示例

格式字元

格式字元具有以下含義;鑑於它們的型別,C 和 Python 值之間的轉換應該是顯而易見的。“標準大小”列是指使用標準大小時打包值的位元組大小;也就是說,當格式字串以 '<''>''!''=' 之一開頭時。當使用本地大小時,打包值的大小取決於平臺。

格式

C 型別

Python 型別

標準大小

備註

x

填充位元組

無值

(7)

c

char

長度為 1 的位元組

1

b

signed char

整數

1

(1), (2)

B

unsigned char

整數

1

(2)

?

_Bool

布林值

1

(1)

h

short

整數

2

(2)

H

unsigned short

整數

2

(2)

i

int

整數

4

(2)

I

unsigned int

整數

4

(2)

l

long

整數

4

(2)

L

unsigned long

整數

4

(2)

q

long long

整數

8

(2)

Q

unsigned long long

整數

8

(2)

n

ssize_t

整數

(3)

N

size_t

整數

(3)

e

(6)

float

2

(4)

f

float

float

4

(4)

d

double

float

8

(4)

s

char[]

位元組

(9)

p

char[]

位元組

(8)

P

void*

整數

(5)

在 3.3 版本中更改: 添加了對 'n''N' 格式的支援。

在 3.6 版本中更改: 添加了對 'e' 格式的支援。

備註

  1. '?' 轉換程式碼對應於 C99 標準以來定義的 C 標準 _Bool 型別。在標準模式下,它由一個位元組表示。

  2. 當嘗試使用任何整數轉換程式碼打包非整數時,如果該非整數具有 __index__() 方法,則會呼叫該方法將引數轉換為整數,然後再打包。

    在 3.2 版本中更改: 為非整數添加了對 __index__() 方法的使用。

  3. 'n''N' 轉換程式碼僅適用於本地大小(選擇為預設值或使用 '@' 位元組順序字元)。對於標準大小,您可以使用適合您應用程式的任何其他整數格式。

  4. 對於 'f''d''e' 轉換程式碼,打包的表示形式使用 IEEE 754 binary32、binary64 或 binary16 格式(分別用於 'f''d''e'),而不管平臺使用的浮點格式如何。

  5. 'P' 格式字元僅適用於本地位元組順序(選擇為預設值或使用 '@' 位元組順序字元)。位元組順序字元 '=' 選擇根據主機系統使用小端或大端排序。struct 模組不將其解釋為本地排序,因此 'P' 格式不可用。

  6. IEEE 754 binary16“半精度”型別是在 IEEE 754 標準的 2008 年修訂版中引入的。它有一個符號位、一個 5 位指數和 11 位精度(顯式儲存 10 位),並且可以表示大約 6.1e-056.5e+04 之間的全精度數字。C 編譯器沒有廣泛支援此型別:在典型的機器上,無符號短整數可用於儲存,但不能用於數學運算。有關更多資訊,請參閱 Wikipedia 上關於半精度浮點格式的頁面。

  7. 打包時,'x' 插入一個 NUL 位元組。

  8. 'p' 格式字元編碼一個“Pascal 字串”,這意味著一個儲存在固定數量位元組中的短的可變長度字串,由計數給出。儲存的第一個位元組是字串的長度,或 255(以較小者為準)。字串的位元組緊隨其後。如果傳遞給 pack() 的字串太長(長於計數減 1),則僅儲存字串的前導 count-1 個位元組。如果字串短於 count-1,則用空位元組填充,以便總共使用正好計數個位元組。請注意,對於 unpack()'p' 格式字元消耗 count 個位元組,但返回的字串永遠不能包含超過 255 個位元組。

  9. 對於 '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

對於 '?' 格式字元,返回值是 TrueFalse。打包時,使用引數物件的真值。將打包本地或標準布林表示形式中的 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'

另請參閱

模組 array

同類資料的緊湊二進位制儲存。

模組 json

JSON 編碼器和解碼器。

模組 pickle

Python 物件序列化。

應用

struct 模組主要有兩種應用:在應用程式內或使用同一編譯器編譯的另一個應用程式(原生格式)之間進行 Python 和 C 程式碼的資料交換,以及使用約定的資料佈局(標準格式)在應用程式之間進行資料交換。一般來說,為這兩個領域構建的格式字串是不同的。

原生格式

當構建模仿原生布局的格式字串時,編譯器和機器架構決定位元組順序和填充。在這種情況下,應使用 @ 格式字元來指定原生位元組順序和資料大小。內部填充位元組通常會自動插入。格式字串末尾可能需要零重複格式程式碼,以便向上舍入到正確的位元組邊界,從而正確對齊連續的資料塊。

考慮以下兩個簡單的示例(在 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(v1, v2, ...)

與使用已編譯格式的 pack() 函式相同。(len(result) 將等於 size。)

pack_into(buffer, offset, v1, v2, ...)

與使用已編譯格式的 pack_into() 函式相同。

unpack(buffer)

與使用已編譯格式的 unpack() 函式相同。緩衝區的位元組大小必須等於 size

unpack_from(buffer, offset=0)

與使用已編譯格式的 unpack_from() 函式相同。從位置 *offset* 開始,緩衝區的位元組大小必須至少為 size

iter_unpack(buffer)

與使用已編譯格式的 iter_unpack() 函式相同。緩衝區的位元組大小必須是 size 的倍數。

在 3.4 版本中新增。

format

用於構造此 Struct 物件的格式字串。

在 3.7 版本中變更: 格式字串型別現在是 str 而不是 bytes

size

format 對應的結構的計算大小(因此也是 pack() 方法生成的位元組物件的大小)。

在 3.13 版本中變更: struct 的 *repr()* 已更改。它現在是

>>> Struct('i')
Struct('i')