溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python讀寫二進制數(shù)組數(shù)據(jù)的方法

發(fā)布時間:2020-08-03 09:17:39 來源:億速云 閱讀:406 作者:小豬 欄目:開發(fā)技術(shù)

這篇文章主要講解了Python讀寫二進制數(shù)組數(shù)據(jù)的方法,內(nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。

問題

你想讀寫一個二進制數(shù)組的結(jié)構(gòu)化數(shù)據(jù)到Python元組中。

解決方案

可以使用 struct 模塊處理二進制數(shù)據(jù)。 下面是一段示例代碼將一個Python元組列表寫入一個二進制文件,并使用 struct 將每個元組編碼為一個結(jié)構(gòu)體。

from struct import Struct
def write_records(records, format, f):
  '''
  Write a sequence of tuples to a binary file of structures.
  '''
  record_struct = Struct(format)
  for r in records:
    f.write(record_struct.pack(*r))

# Example
if __name__ == '__main__':
  records = [ (1, 2.3, 4.5),
        (6, 7.8, 9.0),
        (12, 13.4, 56.7) ]
  with open('data.b', 'wb') as f:
    write_records(records, '<idd', f)

有很多種方法來讀取這個文件并返回一個元組列表。 首先,如果你打算以塊的形式增量讀取文件,你可以這樣做:

from struct import Struct

def read_records(format, f):
  record_struct = Struct(format)
  chunks = iter(lambda: f.read(record_struct.size), b'')
  return (record_struct.unpack(chunk) for chunk in chunks)

# Example
if __name__ == '__main__':
  with open('data.b','rb') as f:
    for rec in read_records('<idd', f):
      # Process rec
      ...

如果你想將整個文件一次性讀取到一個字節(jié)字符串中,然后在分片解析。那么你可以這樣做:

from struct import Struct

def unpack_records(format, data):
  record_struct = Struct(format)
  return (record_struct.unpack_from(data, offset)
      for offset in range(0, len(data), record_struct.size))

# Example
if __name__ == '__main__':
  with open('data.b', 'rb') as f:
    data = f.read()
  for rec in unpack_records('<idd', data):
    # Process rec
    ...

兩種情況下的結(jié)果都是一個可返回用來創(chuàng)建該文件的原始元組的可迭代對象。

討論

對于需要編碼和解碼二進制數(shù)據(jù)的程序而言,通常會使用 struct 模塊。 為了聲明一個新的結(jié)構(gòu)體,只需要像這樣創(chuàng)建一個 Struct 實例即可:

# Little endian 32-bit integer, two double precision floats
record_struct = Struct('<idd')

結(jié)構(gòu)體通常會使用一些結(jié)構(gòu)碼值i, d, f等 [參考 Python文檔 ]。 這些代碼分別代表某個特定的二進制數(shù)據(jù)類型如32位整數(shù),64位浮點數(shù),32位浮點數(shù)等。 第一個字符 < 指定了字節(jié)順序。在這個例子中,它表示”低位在前”。 更改這個字符為 > 表示高位在前,或者是 ! 表示網(wǎng)絡(luò)字節(jié)順序。

產(chǎn)生的 Struct 實例有很多屬性和方法用來操作相應(yīng)類型的結(jié)構(gòu)。 size 屬性包含了結(jié)構(gòu)的字節(jié)數(shù),這在I/O操作時非常有用。 pack() unpack() 方法被用來打包和解包數(shù)據(jù)。比如:

>>> from struct import Struct
>>> record_struct = Struct('<idd')
>>> record_struct.size
20
>>> record_struct.pack(1, 2.0, 3.0)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@'
>>> record_struct.unpack(_)
(1, 2.0, 3.0)
>>>

有時候你還會看到 pack() unpack() 操作以模塊級別函數(shù)被調(diào)用,類似下面這樣:

>>> import struct
>>> struct.pack('<idd', 1, 2.0, 3.0)
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@'
>>> struct.unpack('<idd', _)
(1, 2.0, 3.0)
>>>

這樣可以工作,但是感覺沒有實例方法那么優(yōu)雅,特別是在你代碼中同樣的結(jié)構(gòu)出現(xiàn)在多個地方的時候。 通過創(chuàng)建一個 Struct 實例,格式代碼只會指定一次并且所有的操作被集中處理。 這樣一來代碼維護就變得更加簡單了(因為你只需要改變一處代碼即可)。

讀取二進制結(jié)構(gòu)的代碼要用到一些非常有趣而優(yōu)美的編程技巧。 在函數(shù) read_records 中,iter() 被用來創(chuàng)建一個返回固定大小數(shù)據(jù)塊的迭代器。 這個迭代器會不斷的調(diào)用一個用戶提供的可調(diào)用對象(比如 lambda: f.read(record_struct.size) ), 直到它返回一個特殊的值(如b'‘),這時候迭代停止。例如:

>>> f = open('data.b', 'rb')
>>> chunks = iter(lambda: f.read(20), b'')
>>> chunks
<callable_iterator object at 0x10069e6d0>
>>> for chk in chunks:
... print(chk)
...
b'\x01\x00\x00\x00ffffff\x02@\x00\x00\x00\x00\x00\x00\x12@'
b'\x06\x00\x00\x00333333\x1f@\x00\x00\x00\x00\x00\x00"@'
b'\x0c\x00\x00\x00\xcd\xcc\xcc\xcc\xcc\xcc*@\x9a\x99\x99\x99\x99YL@'
>>>

如你所見,創(chuàng)建一個可迭代對象的一個原因是它能允許使用一個生成器推導(dǎo)來創(chuàng)建記錄。 如果你不使用這種技術(shù),那么代碼可能會像下面這樣:

def read_records(format, f):
  record_struct = Struct(format)
  while True:
    chk = f.read(record_struct.size)
    if chk == b'':
      break
    yield record_struct.unpack(chk)

在函數(shù) unpack_records() 中使用了另外一種方法 unpack_from() 。 unpack_from() 對于從一個大型二進制數(shù)組中提取二進制數(shù)據(jù)非常有用, 因為它不會產(chǎn)生任何的臨時對象或者進行內(nèi)存復(fù)制操作。 你只需要給它一個字節(jié)字符串(或數(shù)組)和一個字節(jié)偏移量,它會從那個位置開始直接解包數(shù)據(jù)。

如果你使用 unpack() 來代替 unpack_from() , 你需要修改代碼來構(gòu)造大量的小的切片以及進行偏移量的計算。比如:

def unpack_records(format, data):
  record_struct = Struct(format)
  return (record_struct.unpack(data[offset:offset + record_struct.size])
      for offset in range(0, len(data), record_struct.size))

這種方案除了代碼看上去很復(fù)雜外,還得做很多額外的工作,因為它執(zhí)行了大量的偏移量計算, 復(fù)制數(shù)據(jù)以及構(gòu)造小的切片對象。 如果你準(zhǔn)備從讀取到的一個大型字節(jié)字符串中解包大量的結(jié)構(gòu)體的話,unpack_from() 會表現(xiàn)的更出色。

在解包的時候,collections 模塊中的命名元組對象或許是你想要用到的。 它可以讓你給返回元組設(shè)置屬性名稱。例如:

from collections import namedtuple

Record = namedtuple('Record', ['kind','x','y'])

with open('data.p', 'rb') as f:
  records = (Record(*r) for r in read_records('<idd', f))

for r in records:
  print(r.kind, r.x, r.y)

如果你的程序需要處理大量的二進制數(shù)據(jù),你最好使用 numpy 模塊。 例如,你可以將一個二進制數(shù)據(jù)讀取到一個結(jié)構(gòu)化數(shù)組中而不是一個元組列表中。就像下面這樣:

>>> import numpy as np
>>> f = open('data.b', 'rb')
>>> records = np.fromfile(f, dtype='<i,<d,<d')
>>> records
array([(1, 2.3, 4.5), (6, 7.8, 9.0), (12, 13.4, 56.7)],
dtype=[('f0', '<i4'), ('f1', '<f8'), ('f2', '<f8')])
>>> records[0]
(1, 2.3, 4.5)
>>> records[1]
(6, 7.8, 9.0)
>>>

最后提一點,如果你需要從已知的文件格式(如圖片格式,圖形文件,HDF5等)中讀取二進制數(shù)據(jù)時, 先檢查看看Python是不是已經(jīng)提供了現(xiàn)存的模塊。因為不到萬不得已沒有必要去重復(fù)造輪子。

看完上述內(nèi)容,是不是對Python讀寫二進制數(shù)組數(shù)據(jù)的方法有進一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI