## 問題
你需要讀取包含嵌套或者可變長記錄集合的復雜二進制格式的數據。這些數據可能包含圖片、視頻、電子地圖文件等。
## 解決方案
`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` 源碼同樣也很有趣,它提供了對定義數據結構、數據結構嵌套這些相似功能的支持。
- Copyright
- 前言
- 第一章:數據結構和算法
- 1.1 解壓序列賦值給多個變量
- 1.2 解壓可迭代對象賦值給多個變量
- 1.3 保留最后N個元素
- 1.4 查找最大或最小的N個元素
- 1.5 實現一個優先級隊列
- 1.6 字典中的鍵映射多個值
- 1.7 字典排序
- 1.8 字典的運算
- 1.9 查找兩字典的相同點
- 1.10 刪除序列相同元素并保持順序
- 1.11 命名切片
- 1.12 序列中出現次數最多的元素
- 1.13 通過某個關鍵字排序一個字典列表
- 1.14 排序不支持原生比較的對象
- 1.15 通過某個字段將記錄分組
- 1.16 過濾序列元素
- 1.17 從字典中提取子集
- 1.18 映射名稱到序列元素
- 1.19 轉換并同時計算數據
- 1.20 合并多個字典或映射
- 第二章:字符串和文本
- 2.1 使用多個界定符分割字符串
- 2.2 字符串開頭或結尾匹配
- 2.3 用Shell通配符匹配字符串
- 2.4 字符串匹配和搜索
- 2.5 字符串搜索和替換
- 2.6 字符串忽略大小寫的搜索替換
- 2.7 最短匹配模式
- 2.8 多行匹配模式
- 2.9 將Unicode文本標準化
- 2.10 在正則式中使用Unicode
- 2.11 刪除字符串中不需要的字符
- 2.12 審查清理文本字符串
- 2.13 字符串對齊
- 2.14 合并拼接字符串
- 2.15 字符串中插入變量
- 2.16 以指定列寬格式化字符串
- 2.17 在字符串中處理html和xml
- 2.18 字符串令牌解析
- 2.19 實現一個簡單的遞歸下降分析器
- 2.20 字節字符串上的字符串操作
- 第三章:數字日期和時間
- 3.1 數字的四舍五入
- 3.2 執行精確的浮點數運算
- 3.3 數字的格式化輸出
- 3.4 二八十六進制整數
- 3.5 字節到大整數的打包與解包
- 3.6 復數的數學運算
- 3.7 無窮大與NaN
- 3.8 分數運算
- 3.9 大型數組運算
- 3.10 矩陣與線性代數運算
- 3.11 隨機選擇
- 3.12 基本的日期與時間轉換
- 3.13 計算最后一個周五的日期
- 3.14 計算當前月份的日期范圍
- 3.15 字符串轉換為日期
- 3.16 結合時區的日期操作
- 第四章:迭代器與生成器
- 4.1 手動遍歷迭代器
- 4.2 代理迭代
- 4.3 使用生成器創建新的迭代模式
- 4.4 實現迭代器協議
- 4.5 反向迭代
- 4.6 帶有外部狀態的生成器函數
- 4.7 迭代器切片
- 4.8 跳過可迭代對象的開始部分
- 4.9 排列組合的迭代
- 4.10 序列上索引值迭代
- 4.11 同時迭代多個序列
- 4.12 不同集合上元素的迭代
- 4.13 創建數據處理管道
- 4.14 展開嵌套的序列
- 4.15 順序迭代合并后的排序迭代對象
- 4.16 迭代器代替while無限循環
- 第五章:文件與IO
- 5.1 讀寫文本數據
- 5.2 打印輸出至文件中
- 5.3 使用其他分隔符或行終止符打印
- 5.4 讀寫字節數據
- 5.5 文件不存在才能寫入
- 5.6 字符串的I/O操作
- 5.7 讀寫壓縮文件
- 5.8 固定大小記錄的文件迭代
- 5.9 讀取二進制數據到可變緩沖區中
- 5.10 內存映射的二進制文件
- 5.11 文件路徑名的操作
- 5.12 測試文件是否存在
- 5.13 獲取文件夾中的文件列表
- 5.14 忽略文件名編碼
- 5.15 打印不合法的文件名
- 5.16 增加或改變已打開文件的編碼
- 5.17 將字節寫入文本文件
- 5.18 將文件描述符包裝成文件對象
- 5.19 創建臨時文件和文件夾
- 5.20 與串行端口的數據通信
- 5.21 序列化Python對象
- 第六章:數據編碼和處理
- 6.1 讀寫CSV數據
- 6.2 讀寫JSON數據
- 6.3 解析簡單的XML數據
- 6.4 增量式解析大型XML文件
- 6.5 將字典轉換為XML
- 6.6 解析和修改XML
- 6.7 利用命名空間解析XML文檔
- 6.8 與關系型數據庫的交互
- 6.9 編碼和解碼十六進制數
- 6.10 編碼解碼Base64數據
- 6.11 讀寫二進制數組數據
- 6.12 讀取嵌套和可變長二進制數據
- 6.13 數據的累加與統計操作
- 第七章:函數
- 7.1 可接受任意數量參數的函數
- 7.2 只接受關鍵字參數的函數
- 7.3 給函數參數增加元信息
- 7.4 返回多個值的函數
- 7.5 定義有默認參數的函數
- 7.6 定義匿名或內聯函數
- 7.7 匿名函數捕獲變量值
- 7.8 減少可調用對象的參數個數
- 7.9 將單方法的類轉換為函數
- 7.10 帶額外狀態信息的回調函數
- 7.11 內聯回調函數
- 7.12 訪問閉包中定義的變量
- 第八章:類與對象
- 8.1 改變對象的字符串顯示
- 8.2 自定義字符串的格式化
- 8.3 讓對象支持上下文管理協議
- 8.4 創建大量對象時節省內存方法
- 8.5 在類中封裝屬性名
- 8.6 創建可管理的屬性
- 8.7 調用父類方法
- 8.8 子類中擴展property
- 8.9 創建新的類或實例屬性
- 8.10 使用延遲計算屬性
- 8.11 簡化數據結構的初始化
- 8.12 定義接口或者抽象基類
- 8.13 實現數據模型的類型約束
- 8.14 實現自定義容器
- 8.15 屬性的代理訪問
- 8.16 在類中定義多個構造器
- 8.17 創建不調用init方法的實例
- 8.18 利用Mixins擴展類功能
- 8.19 實現狀態對象或者狀態機
- 8.20 通過字符串調用對象方法
- 8.21 實現訪問者模式
- 8.22 不用遞歸實現訪問者模式
- 8.23 循環引用數據結構的內存管理
- 8.24 讓類支持比較操作
- 8.25 創建緩存實例
- 第九章:元編程
- 9.1 在函數上添加包裝器
- 9.2 創建裝飾器時保留函數元信息
- 9.3 解除一個裝飾器
- 9.4 定義一個帶參數的裝飾器
- 9.5 可自定義屬性的裝飾器
- 9.6 帶可選參數的裝飾器
- 9.7 利用裝飾器強制函數上的類型檢查
- 9.8 將裝飾器定義為類的一部分
- 9.9 將裝飾器定義為類
- 9.10 為類和靜態方法提供裝飾器
- 9.11 裝飾器為被包裝函數增加參數
- 9.12 使用裝飾器擴充類的功能
- 9.13 使用元類控制實例的創建
- 9.14 捕獲類的屬性定義順序
- 9.15 定義有可選參數的元類
- 9.16 *args和**kwargs的強制參數簽名
- 9.17 在類上強制使用編程規約
- 9.18 以編程方式定義類
- 9.19 在定義的時候初始化類的成員
- 9.20 利用函數注解實現方法重載
- 9.21 避免重復的屬性方法
- 9.22 定義上下文管理器的簡單方法
- 9.23 在局部變量域中執行代碼
- 9.24 解析與分析Python源碼
- 9.25 拆解Python字節碼
- 第十章:模塊與包
- 10.1 構建一個模塊的層級包
- 10.2 控制模塊被全部導入的內容
- 10.3 使用相對路徑名導入包中子模塊
- 10.4 將模塊分割成多個文件
- 10.5 利用命名空間導入目錄分散的代碼
- 10.6 重新加載模塊
- 10.7 運行目錄或壓縮文件
- 10.8 讀取位于包中的數據文件
- 10.9 將文件夾加入到sys.path
- 10.10 通過字符串名導入模塊
- 10.11 通過導入鉤子遠程加載模塊
- 10.12 導入模塊的同時修改模塊
- 10.13 安裝私有的包
- 10.14 創建新的Python環境
- 10.15 分發包
- 第十一章:網絡與Web編程
- 11.1 作為客戶端與HTTP服務交互
- 11.2 創建TCP服務器
- 11.3 創建UDP服務器
- 11.4 通過CIDR地址生成對應的IP地址集
- 11.5 生成一個簡單的REST接口
- 11.6 通過XML-RPC實現簡單的遠程調用
- 11.7 在不同的Python解釋器之間交互
- 11.8 實現遠程方法調用
- 11.9 簡單的客戶端認證
- 11.10 在網絡服務中加入SSL
- 11.11 進程間傳遞Socket文件描述符
- 11.12 理解事件驅動的IO
- 11.13 發送與接收大型數組
- 第十二章:并發編程
- 12.1 啟動與停止線程
- 12.2 判斷線程是否已經啟動
- 12.3 線程間的通信
- 12.4 給關鍵部分加鎖
- 12.5 防止死鎖的加鎖機制
- 12.6 保存線程的狀態信息
- 12.7 創建一個線程池
- 12.8 簡單的并行編程
- 12.9 Python的全局鎖問題
- 12.10 定義一個Actor任務
- 12.11 實現消息發布/訂閱模型
- 12.12 使用生成器代替線程
- 12.13 多個線程隊列輪詢
- 12.14 在Unix系統上面啟動守護進程
- 第十三章:腳本編程與系統管理
- 13.1 通過重定向/管道/文件接受輸入
- 13.2 終止程序并給出錯誤信息
- 13.3 解析命令行選項
- 13.4 運行時彈出密碼輸入提示
- 13.5 獲取終端的大小
- 13.6 執行外部命令并獲取它的輸出
- 13.7 復制或者移動文件和目錄
- 13.8 創建和解壓壓縮文件
- 13.9 通過文件名查找文件
- 13.10 讀取配置文件
- 13.11 給簡單腳本增加日志功能
- 13.12 給內庫增加日志功能
- 13.13 記錄程序執行的時間
- 13.14 限制內存和CPU的使用量
- 13.15 啟動一個WEB瀏覽器
- 第十四章:測試調試和異常
- 14.1 測試輸出到標準輸出上
- 14.2 在單元測試中給對象打補丁
- 14.3 在單元測試中測試異常情況
- 14.4 將測試輸出用日志記錄到文件中
- 14.5 忽略或者期望測試失敗
- 14.6 處理多個異常
- 14.7 捕獲所有異常
- 14.8 創建自定義異常
- 14.9 捕獲異常后拋出另外的異常
- 14.10 重新拋出最后的異常
- 14.11 輸出警告信息
- 14.12 調試基本的程序崩潰錯誤
- 14.13 給你的程序做基準測試
- 14.14 讓你的程序跑的更快
- 第十五章:C語言擴展
- 15.1 使用ctypes訪問C代碼
- 15.2 簡單的C擴展模塊
- 15.3 一個操作數組的擴展函數
- 15.4 在C擴展模塊中操作隱形指針
- 15.5 從擴張模塊中定義和導出C的API
- 15.6 從C語言中調用Python代碼
- 15.7 從C擴展中釋放全局鎖
- 15.8 C和Python中的線程混用
- 15.9 用WSIG包裝C代碼
- 15.10 用Cython包裝C代碼
- 15.11 用Cython寫高性能的數組操作
- 15.12 將函數指針轉換為可調用對象
- 15.13 傳遞NULL結尾的字符串給C函數庫
- 15.14 傳遞Unicode字符串給C函數庫
- 15.15 C字符串轉換為Python字符串
- 15.16 不確定編碼格式的C字符串
- 15.17 傳遞文件名給C擴展
- 15.18 傳遞已打開的文件給C擴展
- 15.19 從C語言中讀取類文件對象
- 15.20 處理C語言中的可迭代對象
- 15.21 診斷分析代碼錯誤
- 附錄A
- 關于譯者
- Roadmap