<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 問題 你需要讀取包含嵌套或者可變長記錄集合的復雜二進制格式的數據。這些數據可能包含圖片、視頻、電子地圖文件等。 ## 解決方案 `struct` 模塊可被用來編碼/解碼幾乎所有類型的二進制的數據結構。為了解釋清楚這種數據,假設你用下面的Python數據結構來表示一個組成一系列多邊形的點的集合: 現在假設這個數據被編碼到一個以下列頭部開始的二進制文件中去了: +------+--------+------------------------------------+ |Byte | Type | Description | +======+========+====================================+ |0 | int | File code (0x1234, little endian) | +------+--------+------------------------------------+ |4 | double | Minimum x (little endian) | +------+--------+------------------------------------+ |12 | double | Minimum y (little endian) | +------+--------+------------------------------------+ |20 | double | Maximum x (little endian) | +------+--------+------------------------------------+ |28 | double | Maximum y (little endian) | +------+--------+------------------------------------+ |36 | int | Number of polygons (little endian)| +------+--------+------------------------------------+ 緊跟著頭部是一系列的多邊形記錄,編碼格式如下: +------+--------+-------------------------------------------+ |Byte | Type | Description | +======+========+===========================================+ |0 | int | Record length including length (N bytes) | +------+--------+-------------------------------------------+ |4-N | Points | Pairs of (X,Y) coords as doubles | +------+--------+-------------------------------------------+ 為了寫這樣的文件,你可以使用如下的Python代碼: import struct import itertools def write_polys(filename, polys): # Determine bounding box flattened = list(itertools.chain(*polys)) min_x = min(x for x, y in flattened) max_x = max(x for x, y in flattened) min_y = min(y for x, y in flattened) max_y = max(y for x, y in flattened) with open(filename, 'wb') as f: f.write(struct.pack('<iddddi', 0x1234, min_x, min_y, max_x, max_y, len(polys))) for poly in polys: size = len(poly) * struct.calcsize('<dd') f.write(struct.pack('<i', size + 4)) for pt in poly: f.write(struct.pack('<dd', *pt)) 將數據讀取回來的時候,可以利用函數 `struct.unpack()` ,代碼很相似,基本就是上面寫操作的逆序。如下: def read_polys(filename): with open(filename, 'rb') as f: # Read the header header = f.read(40) file_code, min_x, min_y, max_x, max_y, num_polys = \ struct.unpack('<iddddi', header) polys = [] for n in range(num_polys): pbytes, = struct.unpack('<i', f.read(4)) poly = [] for m in range(pbytes // 16): pt = struct.unpack('<dd', f.read(16)) poly.append(pt) polys.append(poly) return polys 盡管這個代碼可以工作,但是里面混雜了很多讀取、解包數據結構和其他細節的代碼。如果用這樣的代碼來處理真實的數據文件,那未免也太繁雜了點。因此很顯然應該有另一種解決方法可以簡化這些步驟,讓程序員只關注自最重要的事情。 在本小節接下來的部分,我會逐步演示一個更加優秀的解析字節數據的方案。目標是可以給程序員提供一個高級的文件格式化方法,并簡化讀取和解包數據的細節。但是我要先提醒習啊你,本小節接下來的部分代碼應該是整本書中最復雜最高級的例子,使用了大量的面向對象編程和元編程技術。一定要仔細的閱讀我們的討論部分,另外也要參考下其他章節內容。 首先,當讀取字節數據的時候,通常在文件開始部分會包含文件頭和其他的數據結構。盡管struct模塊可以解包這些數據到一個元組中去,另外一種表示這種信息的方式就是使用一個類。就像下面這樣: import struct class StructField: ''' Descriptor representing a simple structure field ''' def __init__(self, format, offset): self.format = format self.offset = offset def __get__(self, instance, cls): if instance is None: return self else: 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) 這里我們使用了一個描述器來表示每個結構字段,每個描述器包含一個結構兼容格式的代碼以及一個字節偏移量,存儲在內部的內存緩沖中。在 `__get__()` 方法中,`struct.unpack_from()`函數被用來從緩沖中解包一個值,省去了額外的分片或復制操作步驟。 `Structure` 類就是一個基礎類,接受字節數據并存儲在內部的內存緩沖中,并被 `StructField` 描述器使用。這里使用了 `memoryview()` ,我們會在后面詳細講解它是用來干嘛的。 使用這個代碼,你現在就能定義一個高層次的結構對象來表示上面表格信息所期望的文件格式。例如: class PolyHeader(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) 下面的例子利用這個類來讀取之前我們寫入的多邊形數據的頭部數據: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader(f.read(40)) >>> phead.file_code == 0x1234 True >>> phead.min_x 0.5 >>> phead.min_y 0.5 >>> phead.max_x 7.0 >>> phead.max_y 9.2 >>> phead.num_polys 3 >>> 這個很有趣,不過這種方式還是有一些煩人的地方。首先,盡管你獲得了一個類接口的便利,但是這個代碼還是有點臃腫,還需要使用者指定很多底層的細節(比如重復使用 `StructField` ,指定偏移量等)。另外,返回的結果類同樣確實一些便利的方法來計算結構的總數。 任何時候只要你遇到了像這樣冗余的類定義,你應該考慮下使用類裝飾器或元類。元類有一個特性就是它能夠被用來填充許多低層的實現細節,從而釋放使用者的負擔。下面我來舉個例子,使用元類稍微改造下我們的 `Structure` 類: class StructureMeta(type): ''' Metaclass that automatically creates StructField 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 Structure(metaclass=StructureMeta): def __init__(self, bytedata): self._buffer = bytedata @classmethod def from_file(cls, f): return cls(f.read(cls.struct_size)) 使用新的 `Structure` 類,你可以像下面這樣定義一個結構: class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), ('d', 'min_x'), ('d', 'min_y'), ('d', 'max_x'), ('d', 'max_y'), ('i', 'num_polys') ] 正如你所見,這樣寫就簡單多了。我們添加的類方法 `from_file()`讓我們在不需要知道任何數據的大小和結構的情況下就能輕松的從文件中讀取數據。比如: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> phead.min_x 0.5 >>> phead.min_y 0.5 >>> phead.max_x 7.0 >>> phead.max_y 9.2 >>> phead.num_polys 3 >>> 一旦你開始使用了元類,你就可以讓它變得更加智能。例如,假設你還想支持嵌套的字節結構,下面是對前面元類的一個小的改進,提供了一個新的輔助描述器來達到想要的效果: class NestedStruct: ''' Descriptor representing a nested structure ''' def __init__(self, name, struct_type, offset): self.name = name self.struct_type = struct_type self.offset = offset def __get__(self, instance, cls): if instance is None: return self else: data = instance._buffer[self.offset: self.offset+self.struct_type.struct_size] result = self.struct_type(data) # Save resulting structure back on instance to avoid # further recomputation of this step setattr(instance, self.name, result) return result class StructureMeta(type): ''' Metaclass that automatically creates StructField descriptors ''' def __init__(self, clsname, bases, clsdict): fields = getattr(self, '_fields_', []) byte_order = '' offset = 0 for format, fieldname in fields: if isinstance(format, StructureMeta): setattr(self, fieldname, NestedStruct(fieldname, format, offset)) offset += format.struct_size else: 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) 在這段代碼中,`NestedStruct` 描述器被用來疊加另外一個定義在某個內存區域上的結構。它通過將原始內存緩沖進行切片操作后實例化給定的結構類型。由于底層的內存緩沖區是通過一個內存視圖初始化的,所以這種切片操作不會引發任何的額外的內存復制。相反,它僅僅就是之前的內存的一個疊加而已。另外,為了防止重復實例化,通過使用和8.10小節同樣的技術,描述器保存了該實例中的內部結構對象。 使用這個新的修正版,你就可以像下面這樣編寫: class Point(Structure): _fields_ = [ ('<d', 'x'), ('d', 'y') ] class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), (Point, 'min'), # nested struct (Point, 'max'), # nested struct ('i', 'num_polys') ] 令人驚訝的是,它也能按照預期的正常工作,我們實際操作下: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> phead.min # Nested structure <__main__.Point object at 0x1006a48d0> >>> phead.min.x 0.5 >>> phead.min.y 0.5 >>> phead.max.x 7.0 >>> phead.max.y 9.2 >>> phead.num_polys 3 >>> 到目前為止,一個處理定長記錄的框架已經寫好了。但是如果組件記錄是變長的呢?比如,多邊形文件包含變長的部分。 一種方案是寫一個類來表示字節數據,同時寫一個工具函數來通過多少方式解析內容。跟6.11小節的代碼很類似: class SizedRecord: def __init__(self, bytedata): self._buffer = memoryview(bytedata) @classmethod def from_file(cls, f, size_fmt, includes_size=True): sz_nbytes = struct.calcsize(size_fmt) sz_bytes = f.read(sz_nbytes) sz, = struct.unpack(size_fmt, sz_bytes) buf = f.read(sz - includes_size * sz_nbytes) return cls(buf) def iter_as(self, code): if isinstance(code, str): s = struct.Struct(code) for off in range(0, len(self._buffer), s.size): yield s.unpack_from(self._buffer, off) elif isinstance(code, StructureMeta): size = code.struct_size for off in range(0, len(self._buffer), size): data = self._buffer[off:off+size] yield code(data) 類方法 `SizedRecord.from_file()` 是一個工具,用來從一個文件中讀取帶大小前綴的數據塊,這也是很多文件格式常用的方式。作為輸入,它接受一個包含大小編碼的結構格式編碼,并且也是自己形式。可選的 `includes_size` 參數指定了字節數是否包含頭部大小。下面是一個例子教你怎樣使用從多邊形文件中讀取單獨的多邊形數據: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.num_polys 3 >>> polydata = [ SizedRecord.from_file(f, '<i') ... for n in range(phead.num_polys) ] >>> polydata [<__main__.SizedRecord object at 0x1006a4d50>, <__main__.SizedRecord object at 0x1006a4f50>, <__main__.SizedRecord object at 0x10070da90>] >>> 可以看出,`SizedRecord` 實例的內容還沒有被解析出來。可以使用 `iter_as()` 方法來達到目的,這個方法接受一個結構格式化編碼或者是 `Structure` 類作為輸入。這樣子可以很靈活的去解析數據,例如: >>> for n, poly in enumerate(polydata): ... print('Polygon', n) ... for p in poly.iter_as('<dd'): ... print(p) ... Polygon 0 (1.0, 2.5) (3.5, 4.0) (2.5, 1.5) Polygon 1 (7.0, 1.2) (5.1, 3.0) (0.5, 7.5) (0.8, 9.0) Polygon 2 (3.4, 6.3) (1.2, 0.5) (4.6, 9.2) >>> >>> for n, poly in enumerate(polydata): ... print('Polygon', n) ... for p in poly.iter_as(Point): ... print(p.x, p.y) ... Polygon 0 1.0 2.5 3.5 4.0 2.5 1.5 Polygon 1 7.0 1.2 5.1 3.0 0.5 7.5 0.8 9.0 Polygon 2 3.4 6.3 1.2 0.5 4.6 9.2 >>> 將所有這些結合起來,下面是一個 `read_polys()` 函數的另外一個修正版: class Point(Structure): _fields_ = [ ('<d', 'x'), ('d', 'y') ] class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), (Point, 'min'), (Point, 'max'), ('i', 'num_polys') ] def read_polys(filename): polys = [] with open(filename, 'rb') as f: phead = PolyHeader.from_file(f) for n in range(phead.num_polys): rec = SizedRecord.from_file(f, '<i') poly = [ (p.x, p.y) for p in rec.iter_as(Point) ] polys.append(poly) return polys ## 討論 這一節向你展示了許多高級的編程技術,包括描述器,延遲計算,元類,類變量和內存視圖。然而,它們都為了同一個特定的目標服務。 上面的實現的一個主要特征是它是基于懶解包的思想。當一個 `Structure` 實例被創建時,`__init__()` 僅僅只是創建一個字節數據的內存視圖,沒有做其他任何事。特別的,這時候并沒有任何的解包或者其他與結構相關的操作發生。這樣做的一個動機是你可能僅僅只對一個字節記錄的某一小部分感興趣。我們只需要解包你需要訪問的部分,而不是整個文件。 為了實現懶解包和打包,需要使用 `StructField` 描述器類。用戶在 `_fields_` 中列出來的每個屬性都會被轉化成一個 `StructField` 描述器,它將相關結構格式碼和偏移值保存到存儲緩存中。元類 `StructureMeta` 在多個結構類被定義時自動創建了這些描述器。我們使用元類的一個主要原因是它使得用戶非常方便的通過一個高層描述就能指定結構格式,而無需考慮低層的細節問題。 `StructureMeta` 的一個很微妙的地方就是它會固定字節數據順序。也就是說,如果任意的屬性指定了一個字節順序(<表示低位優先 或者 >表示高位優先),那后面所有字段的順序都以這個順序為準。這么做可以幫助避免額外輸入,但是在定義的中間我們仍然可能切換順序的。比如,你可能有一些比較復雜的結構,就像下面這樣: class ShapeFile(Structure): _fields_ = [ ('>i', 'file_code'), # Big endian ('20s', 'unused'), ('i', 'file_length'), ('<i', 'version'), # Little endian ('i', 'shape_type'), ('d', 'min_x'), ('d', 'min_y'), ('d', 'max_x'), ('d', 'max_y'), ('d', 'min_z'), ('d', 'max_z'), ('d', 'min_m'), ('d', 'max_m') ] 之前我們提到過,`memoryview()` 的使用可以幫助我們避免內存的復制。當結構存在嵌套的時候,`memoryviews` 可以疊加同一內存區域上定義的機構的不同部分。這個特性比較微妙,但是它關注的是內存視圖與普通字節數組的切片操作行為。如果你在一個字節字符串或字節數組上執行切片操作,你通常會得到一個數據的拷貝。而內存視圖切片不是這樣的,它僅僅是在已存在的內存上面疊加而已。因此,這種方式更加高效。 還有很多相關的章節可以幫助我們擴展這里討論的方案。參考8.13小節使用描述器構建一個類型系統。8.10小節有更多關于延遲計算屬性值的討論,并且跟NestedStruct描述器的實現也有關。9.19小節有一個使用元類來初始化類成員的例子,和 `StructureMeta` 類非常相似。Python的 `ctypes` 源碼同樣也很有趣,它提供了對定義數據結構、數據結構嵌套這些相似功能的支持。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看