天天看點

剛剛開始學習Python?了解二進制資料處理是必不可少的!

作者:你的老師父

在Python中,我們通常使用文本檔案存儲和處理資料。但是,在某些情況下,文本檔案并不夠用。例如,當需要處理音頻、視訊或圖像等多媒體資料時,它們可能會以二進制格式儲存。此外,在與其他語言(如C++)編寫的程式互動時,也可能需要處理二進制資料。

二進制檔案通常是由一系列位元組組成的,每個位元組由8位(即一個位元組)組成,可以表示0到255之間的整數。在Python中,有幾個子產品可以幫助我們讀寫二進制檔案,包括 struct 子產品、位運算和資料壓縮和解壓。這篇教程将介紹如何使用這些工具來處理二進制資料。

Python 中的 struct 子產品

struct 子產品是Python中處理二進制資料的重要工具。它允許我們将二進制資料轉換為Python對象,或者将Python對象轉換為二進制資料。它提供了一種簡單的方式來處理各種類型的資料,包括整數、浮點數、布爾值、字元串和自定義結構體等。

struct 子產品的作用和優勢

在Python中,我們通常使用内置的資料類型(如整數、浮點數和字元串)來表示資料。這些資料類型在記憶體中的表示方式是固定的,即它們都具有相同的位元組大小和排列順序。

但是,在處理二進制資料時,其表示方式可能與Python中的資料類型不同。例如,一個整數可能由4個位元組組成,這些位元組的排列順序可能是大端(MSB在前)或小端(LSB在前)。如果我們使用内置的資料類型來處理這樣的資料,就需要考慮這些細節,并手工解析位元組序列。這很容易出錯,并且非常繁瑣。

struct 子產品提供了一種簡單的方式來處理這些問題。它可以自動将二進制資料解析為Python對象,并根據需要進行位元組序轉換。它還提供了一種簡單的方式來将Python對象轉換為二進制資料,并使用正确的位元組序。

結構體概念和使用方法

在 struct 子產品中,可以使用結構體來描述二進制資料的格式。結構體是一種自定義資料類型,它指定了二進制資料中每個字段的類型和順序。可以通過結構體将二進制資料轉換為Python對象,或将Python對象轉換為二進制資料。

結構體通常以字元串的形式給出,其中包含一個或多個格式代碼。格式代碼指定了資料類型和位元組順序等資訊。下面是常用的格式代碼:

格式代碼 資料類型
b 有符号位元組
B 無符号位元組
h 有符号短整數(2個位元組)
H 無符号短整數(2個位元組)
i 有符号整數(4個位元組)
I 無符号整數(4個位元組)
q 有符号長整數(8個位元組)
Q 無符号長整數(8個位元組)
f 單精度浮點數(4個位元組)
d 雙精度浮點數(8個位元組)
s 字元串

例如,假設我們有一個包含一個整數和一個浮點數的二進制資料,整數在前,浮點數在後,我們可以使用以下代碼将其解析為Python對象:

import struct

# 定義結構體格式字元串
format_str = "if"

# 讀取二進制資料
with open("data.bin", "rb") as f:
    data = f.read()

# 解析二進制資料
result = struct.unpack(format_str, data)

# 輸出結果
print(result)  # (42, 3.14)
           

這裡,我們首先定義了一個格式字元串 format_str,它包含兩個格式代碼:i 表示一個有符号整數,占據4個位元組,f 表示一個單精度浮點數,占據4個位元組。然後,我們使用 open() 函數打開二進制檔案(注意要以 'rb' 模式打開),并使用 read() 方法讀取其中的所有資料。最後,我們使用 struct.unpack() 函數将二進制資料解析為一個元組,并将其存儲在變量 result 中。

如何使用 struct 子產品進行二進制資料的轉換

除了解析二進制資料之外,struct 子產品還提供了一種簡單的方式來将Python對象轉換為二進制資料。我們可以使用 struct.pack() 函數将一個或多個參數轉換為一個位元組串,該位元組串具有指定的格式。例如,如果要将一個整數和一個浮點數打包成一個位元組串,可以使用以下代碼:

import struct

# 定義結構體格式字元串
format_str = "if"

# 打包資料
data = struct.pack(format_str, 42, 3.14)

# 寫入二進制檔案
with open("output.bin", "wb") as f:
    f.write(data)
           

這裡,我們首先定義了一個格式字元串 format_str,與上面的例子相同。然後,我們使用 struct.pack() 函數将整數和浮點數打包成一個位元組串,并将其存儲在變量 data 中。最後,我們使用 open() 函數打開二進制檔案(注意要以 'wb' 模式打開),并使用 write() 方法将位元組串寫入檔案中。

示例代碼

下面是一個完整的示例代碼,它将一個自定義結構體寫入二進制檔案,然後讀取該檔案并解析其中的資料:

import struct

# 定義自定義結構體
class Point2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# 定義結構體格式字元串
format_str = "dd"

# 建立 Point2D 對象
p = Point2D(3.14, 2.71)

# 将 Point2D 對象打包成位元組串
data = struct.pack(format_str, p.x, p.y)

# 寫入二進制檔案
with open("point.bin", "wb") as f:
    f.write(data)

# 從二進制檔案中讀取資料
with open("point.bin", "rb") as f:
    data = f.read()

# 解析二進制資料
result = struct.unpack(format_str, data)

# 建立新的 Point2D 對象
p2 = Point2D(result[0], result[1])

# 輸出結果
print(p2.x, p2.y)
           

在這個例子中,我們首先定義了一個自定義結構體 Point2D,它包含兩個屬性 x 和 y。然後,我們定義了一個格式字元串 format_str,表示兩個雙精度浮點數。接着,我們建立了一個 Point2D 對象 p,并使用 struct.pack() 函數将其打包成一個位元組串,并将該位元組串寫入檔案中。

接下來,我們使用 open() 函數打開二進制檔案,并使用 read() 方法讀取其中的所有資料。然後,我們使用 struct.unpack() 函數将該位元組串解析為一個元組。最後,我們使用解析出的結果建立一個新的 Point2D 對象 p2,并輸出其中的屬性值。

位運算

除了使用 struct 子產品之外,另一種處理二進制資料的方式是使用位運算。位運算是一種操作二進制資料的方式,它可以對單個位元組或多個位元組進行逐位操作,并産生一個新的二進制數值作為結果。

位運算的基礎知識和應用場景

在計算機中,每個位元組由8個位組成,每個位可能是0或1。在二進制資料進行中,我們通常需要對這些位進行逐位操作,例如檢查某個位是否為1、将某個位設定為1或0、取反某個位元組等等。這就是位運算所涉及的内容。

位運算可以應用于許多領域,包括網絡程式設計、密碼學、圖像處理等。例如,在網絡程式設計中,IP位址通常被表示為32位的二進制數,是以需要使用位運算來提取其子網路遮罩或進行其他操作。在密碼學中,位運算可以用于加密和解密資料。在圖像進行中,位運算可以用于處理像素資料。

Python 中的位運算符及其使用方法

在Python中,有幾個位運算符可供使用。這些運算符用于對整數進行逐位操作,并傳回一個整數作為結果。以下是常用的位運算符:

運算符 描述
& 按位與
| 按位或
^ 按位異或
~ 按位取反
<< 左移
>> 右移

例如,如果要将一個位元組中的第3位設定為1,可以使用以下代碼:

# 将第3位設定為1
b = 0b00001000
b |= (1 << 2)

# 輸出結果
print(bin(b))  # 0b00001100
           

在這個例子中,我們首先定義了一個變量 b,它包含一個位元組的二進制資料。然後,我們使用按位或運算符(|)和左移運算符(<<)将第3位設定為1。最後,我們使用 bin() 函數将修改後的值轉換為二進制字元串,并輸出結果。

如何使用位運算處理二進制資料

除了對單個位元組進行逐位操作之外,位運算還可以應用于多個位元組的資料。例如,如果要提取一個32位的IP位址中的子網路遮罩,可以使用以下代碼:

import socket

# 解析IP位址和子網路遮罩
ip = "192.168.0.1"
netmask = "255.255.255.0"

ip_int = int.from_bytes(socket.inet_aton(ip), byteorder="big")
netmask_int = int.from_bytes(socket.inet_aton(netmask), byteorder="big")

# 提取子網路遮罩
subnet_mask = ip_int & netmask_int

# 輸出結果
print(socket.inet_ntoa(subnet_mask.to_bytes(4, byteorder="big")))  # "192.168.0.0"
           

在這個例子中,我們首先使用 socket 子產品中的 inet_aton() 函數将IP位址和子網路遮罩轉換為32位整數。然後,我們使用按位與運算符(&)提取子網路遮罩。最後,我們使用 inet_ntoa() 函數将二進制資料轉換為點分十進制格式,并輸出結果。

示例代碼

下面是一個完整的示例代碼,它使用位運算将一個位元組中的資料拆分為兩個半位元組,并輸出其十六進制表示:

# 定義位元組和位數
byte = 0xAB
bits_per_half_byte = 4

# 提取左半位元組和右半位元組
left = (byte >> bits_per_half_byte) & ((1 << bits_per_half_byte) - 1)
right = byte & ((1 << bits_per_half_byte) - 1)

# 輸出結果
print(hex(left), hex(right))  # "0xA", "0xB"
           

在這個例子中,我們首先定義了一個位元組 byte 和每個半位元組包含的位數 bits_per_half_byte。然後,我們使用右移運算符(>>)和按位與運算符(&)提取左半位元組和右半位元組。最後,我們使用 hex() 函數将兩個半位元組的值轉換為十六進制字元串,并輸出結果。

總結

本文介紹了如何使用Python處理二進制資料,包括使用 struct 子產品解析和生成二進制資料,以及使用位運算處理單個位元組或多個位元組的資料。這些技術對于網絡程式設計、密碼學、圖像處理等領域都非常重要,掌握這些技能可以讓你更好地了解計算機系統并開發高效的應用程式。