"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 的定義，它總是大端序
。

沒有方法來指示非原生位元組順序（強制位元組交換）；請使用適當的 "'<'"
或 "'>'" 選擇。

註解：

1. 填充只會在連續的結構成員之間自動加入。編碼結構的開頭或結尾不會加入
   填充。

2. 使用非原生大小和對齊時，例如使用 '<'、'>'、'=' 和 '!' 時，不會加入
   填充。

3. 要將結構的結尾對齊到特定型別的對齊需求，請以該型別的碼結束格式，重
   複次數為零。請參見範例。


格式字元
--------

格式字元具有以下意義；在給定型別的情況下，C 和 Python 數值之間的轉換應
該是顯而易見的。「標準大小」欄位指的是使用標準大小時（即當格式字串以
"'<'"、"'>'"、"'!'" 或 "'='" 其中之一開始時）打包數值的位元組大小。使
用原生大小時，打包數值的大小取決於平台。

+----------+----------------------------+----------------------+------------------+--------------+
| 格式     | C Type                     | Python 型別          | 標準大小         | 註解         |
|==========|============================|======================|==================|==============|
| "x"      | 填充位元組                 | 無值                 |                  | (7)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "c"      | char                       | 長度為 1 的位元組    | 1                |              |
+----------+----------------------------+----------------------+------------------+--------------+
| "b"      | signed char                | 整數                 | 1                | (1), (2)     |
+----------+----------------------------+----------------------+------------------+--------------+
| "B"      | unsigned char              | 整數                 | 1                | (2)          |
+----------+----------------------------+----------------------+------------------+--------------+
| "?"      | _Bool                      | 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「半精度 (half precision)」型別是在 2008 年的 IEEE
   754 標準修訂版中引入的。它有一個符號位元 (sign bit)、5 位元指數和
   11 位元精度（明確儲存 10 位元），可以全精度表示大約 "6.1e-05" 到
   "6.5e+04" 之間的數字。此型別未被 C 編譯器廣泛支援：在典型機器上，
   unsigned short 可以用於儲存但不能用於數學運算。請參見 Wikipedia 上
   的半精度浮點數格式頁面以取得更多資訊。

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

8. "'p'" 格式字元編碼一個「Pascal 字串」，意思是儲存在*固定位元組數*中
   的短、長度可變字串，由計數指定。儲存的第一個位元組是字串的長度，或
   與 255 間取較小者。字串的位元組在其後。如果傳遞給 "pack()" 的字串太
   長（長於計數減 1），則只儲存字串的前 "count-1" 個位元組。如果字串短
   於 "count-1"，則用空位元組填充，使得總共使用恰好 count 個位元組。請
   注意，對於 "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"。

對於 "'?'" 格式字元，回傳值是 "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'

也參考:

  "array" 模組
     同質資料的打包二進位儲存。

  "json" 模組
     JSON 編碼器和解碼器。

  "pickle" 模組
     Python 物件序列化。


應用
====

"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(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 版的變更: 結構的 *repr()* 已經改變。現在是：

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