# 《Python Cookbook（第3版）中文版》——6.12　读取嵌套型和大小可变的二进制结构

### 6.12.1　问题

6.12.2　解决方案

struct模块可用来编码和解码几乎任何类型的二进制数据结构。为了说明本节中提到的这种数据，假设我们有一个用Python数据结构表示的点的集合，这些点可用来组成一系列的三角形：

polys = [
[ (1.0, 2.5), (3.5, 4.0), (2.5, 1.5) ],
[ (7.0, 1.2), (5.1, 3.0), (0.5, 7.5), (0.8, 9.0) ],
[ (3.4, 6.3), (1.2, 0.5), (4.6, 9.2) ],
]

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))
# Call it with our polygon data
write_polys('polys.bin', polys)

import struct
with open(filename, 'rb') as f:
file_code, min_x, min_y, max_x, max_y, num_polys = \
polys = []
for n in range(num_polys):
poly = []
for m in range(pbytes // 16):
poly.append(pt)
polys.append(poly)
return polys

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)

Structure类只是用作基类，它接受一些字节数据并保存在由StructField描述符所使用的底层内存缓冲区中。这样一来，在Structure类中用到的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')
True
0.5
0.5
7.0
9.2
3
>>>

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))

class PolyHeader(Structure):
_fields_ = [
('<i', 'file_code'),
('d', 'min_x'),
('d', 'min_y'),
('d', 'max_x'),
('d', 'max_y'),
('i', 'num_polys')
]

>>> f = open('polys.bin', 'rb')
True
0.5
0.5
7.0
9.2
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)

class Point(Structure):
_fields_ = [
('<d', 'x'),
('d', 'y')
]
_fields_ = [
('<i', 'file_code'),
(Point, 'min'), # nested struct
(Point, 'max'), # nested struct
('i', 'num_polys')
]

>>> f = open('polys.bin', 'rb')
True
<__main__.Point object at 0x1006a48d0>
0.5
0.5
7.0
9.2
3
>>>

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, = 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)

>>> f = open('polys.bin', 'rb')
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>]
>>>

>>> 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
>>>

class Point(Structure):
_fields_ = [
('<d', 'x'),
('d', 'y')
]
_fields_ = [
('<i', 'file_code'),
(Point, 'min'),
(Point, 'max'),
('i', 'num_polys')
]
polys = []
with open(filename, 'rb') as f:
rec = SizedRecord.from_file(f, '<i')
poly = [ (p.x, p.y)
for p in rec.iter_as(Point) ]
polys.append(poly)
return polys

### 6.12.3　讨论

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') ]

|
1天前
|

Python图论实战：从零基础到精通DFS与BFS遍历，轻松玩转复杂网络结构
【7月更文挑战第11天】图论在数据科学中扮演关键角色，用于解决复杂网络问题。Python因其易用性和库支持成为实现图算法的首选。本文通过问答形式介绍DFS和BFS，图是节点和边的数据结构，遍历用于搜索和分析。Python中图可表示为邻接表，DFS用递归遍历，BFS借助队列。DFS适用于深度探索，BFS则用于最短路径。提供的代码示例帮助理解如何在Python中应用这两种遍历算法。开始探索图论，解锁更多技术可能！
16 6
|
1天前
|

python的结构
【7月更文挑战第12天】python的结构
5 2
|
1天前
|

【7月更文挑战第11天】在数据结构与算法中，图的遍历如DFS和BFS是解决复杂问题的关键。DFS深入探索直至无路可走，回溯找其他路径，适合找任意解；BFS则逐层扩展，常用于找最短路径。在迷宫问题中，BFS确保找到最短路径，DFS则可能不是最短。Python实现展示了两种方法如何在图（迷宫）中寻找从起点到终点的路径。
8 1
|
5天前
|

【7月更文挑战第7天】Python中的闭包和装饰器是强大工具。闭包是能记住外部作用域变量的内部函数，常用于动态函数创建和数据封装。装饰器是接收函数并返回新函数的函数，用于在不修改原代码的情况下扩展功能，如日志或性能监控。通过示例展示了如何使用它们优化代码结构和提升效率。掌握这两者，能写出更优雅高效的Python代码。
7 0
|
8天前
|

Pandas是Python数据分析的核心库，基于NumPy，提供DataFrame结构处理结构化数据
【7月更文挑战第5天】Pandas是Python数据分析的核心库，基于NumPy，提供DataFrame结构处理结构化数据。它支持缺失值处理（dropna()、fillna())、异常值检测（Z-Score、IQR法）和重复值管理（duplicated()、drop_duplicates()）。此外，数据转换包括类型转换（astype()）、数据标准化（Min-Max、Z-Score）以及类别编码（get_dummies()）。这些功能使得Pandas成为大数据预处理的强大工具。
11 0
|
15天前
|

11 0
|
17天前
|
Python
python分支结构
python分支结构
9 0
|
29天前
|
Python
Python基础教程（第3版）中文版 第20章 项目1： 自动添加标签(纯文本转HTML格式) （笔记2）
Python基础教程（第3版）中文版 第20章 项目1： 自动添加标签(纯文本转HTML格式) （笔记2）
17 0
|
29天前
|
Python
Python基础教程（第3版）中文版 第20章 项目1： 自动添加标签(纯文本转HTML格式) （笔记）
Python基础教程（第3版）中文版 第20章 项目1： 自动添加标签(纯文本转HTML格式) （笔记）
11 0
|
29天前
|

Python基础教程（第3版）中文版 第19章 趣味编程 （笔记）
Python基础教程（第3版）中文版 第19章 趣味编程 （笔记）
17 0