複雜二進位制資料

一枚码农發表於2024-04-27
點選檢視程式碼
# 讀取巢狀型和大小可變的二進位制結構
from itertools import chain
import struct

# 多邊形陣列
polys = [
    [(1.0, 2.1), (2.0, 3.2), (3.0, 4.3)],
    [(1.1, 2.2), (2.1, 3.3), (3.1, 4.4), (4.1, 5.5)],
    [(1.2, 2.3), (2.2, 3.4), (3.1, 4.5)],
]

# 檔案格式
"""
檔案頭
    位元組        型別                描述
    0          int               檔案程式碼(0x1234, 小端)
    4          double            x的最小值(小端)
    12         double            y的最小值(小端)
    20         double            x的最大值(小端)
    28         double            y的最大值(小端)
    36         int               多邊形數量
正文
    位元組        型別                描述
    0          int               記錄長度(N位元組)
    4-N        Points            (X,Y)座標,以浮點數表示
"""


def write_ploys(filename, polys):
    flattened = list(chain(*polys))
    min_x = min(x for x, y in flattened)
    min_y = min(y for x, y in flattened)
    max_x = max(x for x, y in flattened)
    max_x = max(x for x, y in flattened)
    with open(filename, "wb") as f:
        head = struct.pack("<iddddi", 0x1234, min_x, min_y, max_x, max_x, len(polys))
        f.write(head)

        for ploy in polys:
            size = len(ploy) * struct.calcsize("<dd")
            f.write(struct.pack("<i", size + 4))
            for pt in ploy:
                f.write(struct.pack("<dd", *pt))


write_ploys("binary_complex", polys)


def read_polys(filename):
    with open(filename, "rb") as f:
        head = f.read(40)
        file_code, min_x, min_y, max_x, max_y, num_polys = struct.unpack(
            "<iddddi", head
        )
        print(
            "read file head: ", file_code, min_x, min_y, max_x, max_y, num_polys
        )  # read file head:  4660 1.0 2.1 4.1 4.1 3

        polys = []
        for i in range(num_polys):
            poly = []
            (pbytes,) = struct.unpack("<i", f.read(4))
            for j in range(pbytes // 16):
                poly.append(struct.unpack("<dd", f.read(16)))
            polys.append(poly)

        print(
            "read polys: ", polys
        )  # read polys:  [[(1.0, 2.1), (2.0, 3.2), (3.0, 4.3)],
        # [(1.1, 2.2), (2.1, 3.3), (3.1, 4.4), (4.1, 5.5)],
        # [(1.2, 2.3), (2.2, 3.4), (3.1, 4.5)]]


read_polys("binary_complex")


# 上面的程式碼比較亂 對上面程式碼的最佳化
print("-" * 10)


class StructField:
    """Descriptor representing a simple struct field"""

    def __init__(self, format, offset):
        self.format = format
        self.offset = offset

    def __get__(self, instance, cls):
        if instance is None:
            return self
        r = struct.unpack_from(self.format, instance._buffer, self.offset)
        return r[0] if len(r) == 1 else r


class Structure:
    def __init__(self, bytedata):
        self._buffer = memoryview(bytedata)


class PolyHead(Structure):
    file_code = StructField("<i", 0)
    min_x = StructField("<d", 4)
    min_y = StructField("<d", 12)
    max_x = StructField("<d", 20)
    max_y = StructField("<d", 28)
    num_polys = StructField("<i", 36)


with open("binary_complex", "rb") as f:
    phead = PolyHead(f.read())
    print("phead hex file_code: ", hex(phead.file_code))
    print("phead min_x: ", phead.min_x)
    print("phead num_polys", phead.num_polys)

print("-" * 10)


# 上邊這麼寫很有趣, 但這種方法還有問題,程式碼比較冗長,重複使用StructField, 要指定偏移量等
# 任何時候,面對冗長的類定義的時候,應考慮用類裝飾器或者元類進行最佳化
class StructureMeta(type):
    """Metaclass that automatically creates StructFiled descriptors"""

    def __init__(self, clsname, bases, clsdict):
        fields = getattr(self, "_fields_", [])
        byte_order = ""
        offset = 0
        for format, fieldname in fields:
            if format.startswith(("<", ">", "!", "@")):
                byte_order = format[0]
                format = format[1:]
            format = byte_order + format
            setattr(self, fieldname, StructField(format, offset))
            offset += struct.calcsize(format)
        setattr(self, "struct_size", offset)


class StructurePro(metaclass=StructureMeta):
    def __init__(self, bytedata):
        self._buffer = bytedata

    @classmethod
    def from_file(cls, f):
        return cls(f.read(cls.struct_size))


class PolyHeaderPro(StructurePro):
    _fields_ = [
        ("<i", "file_code"),
        ("d", "min_x"),
        ("d", "min_y"),
        ("d", "max_x"),
        ("d", "max_y"),
        ("i", "num_polys"),
    ]


with open("binary_complex", "rb") as f:
    phead_pro = PolyHeaderPro.from_file(f)
    # phead_pro = PolyHeaderPro(f.read())
    print("phead_pro hex file_code: ", hex(phead_pro.file_code))
    print("phead_pro min_x: ", phead_pro.min_x)
    print("phead_pro num_polys", phead_pro.num_polys)

相關文章