11. Python 標準函式庫概覽——第二部份

第二部分涵蓋更多支援專業程式設計所需要的進階模組。這些模組很少出現在小腳本中。

11.1. 輸出格式化 (Output Formatting)

reprlib 模組提供了一個 repr() 的版本,專門用來以簡短的形式顯示大型或深層的巢狀容器:

>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"

pprint 模組能對內建和使用者自定的物件提供更複雜的列印控制,並且是以直譯器可讀的方式。當結果超過一行時,「漂亮的印表機」會加入換行和縮排,以更清楚地顯示資料結構:

>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
...     'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
   'white',
   ['green', 'red']],
  [['magenta', 'yellow'],
   'blue']]]

textwrap 模組能夠格式化文本的段落,以符合指定的螢幕寬度:

>>> import textwrap
>>> doc = """The wrap() method is just like fill() except that it returns
... a list of strings instead of one big string with newlines to separate
... the wrapped lines."""
...
>>> print(textwrap.fill(doc, width=40))
The wrap() method is just like fill()
except that it returns a list of strings
instead of one big string with newlines
to separate the wrapped lines.

locale 模組能存取一個含有特定文化相關資料格式的資料庫。locale 模組的 format 函式有一個 grouping 屬性,可直接以群分隔符 (group separator) 將數字格式化:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv()          # 取得慣例的對映
>>> x = 1234567.8
>>> locale.format_string("%d", x, grouping=True)
'1,234,567'
>>> locale.format_string("%s%.*f", (conv['currency_symbol'],
...                      conv['frac_digits'], x), grouping=True)
'$1,234,567.80'

11.2. 模板化 (Templating)

string 模組包含一個多功能的 Template class,提供的簡化語法適合使用者進行編輯時使用。它容許使用者客製化自己的應用程式,但不必對原應用程式做出變更。

格式化方式是使用佔位符號名稱 (placeholder name),它是由 $ 加上合法的 Python 識別符(字母、數字和下底線)構成。使用大括號包覆佔位符號以允許在後面接上更多的字母和數字而無需插入空格。使用 $$ 將會跳脫為單一字元 $

>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'

如果在 dictionary 或關鍵字引數中未提供某個佔位符號的值,那麼 substitute() method 將引發 KeyError。對於郵件合併 (mail-merge) 類型的應用程式,使用者提供的資料有可能是不完整的,此時使用 safe_substitute() method 會更適當——如果資料有缺少,它會保持佔位符號不變:

>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
  ...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'

Template 的 subclass(子類別)可以指定自訂的分隔符號 (delimiter)。例如,一個相片瀏覽器的批次重新命名功能,可以選擇用百分號作為現在日期、照片序號或檔案格式的佔位符號:

>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
...     delimiter = '%'
...
>>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format):  ')
Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f

>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
...     base, ext = os.path.splitext(filename)
...     newname = t.substitute(d=date, n=i, f=ext)
...     print('{0} --> {1}'.format(filename, newname))

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

模板化的另一個應用,是將程式邏輯與多樣的輸出格式細節分離開來。這樣就可以為 XML 檔案、純文字報表和 HTML 網路報表替換自訂的模板。

11.3. 二進制資料記錄編排 (Binary Data Record Layouts)

struct 模組提供了 pack()unpack() 函式,用於處理可變動長度的二進制記錄格式。以下範例說明,如何在不使用 zipfile 模組的情況下,使用迴圈瀏覽一個 ZIP 檔案中的標頭資訊 (header information)。壓縮程式碼 "H""I" 分別代表兩個和四個位元組的無符號數 (unsigned number)。"<" 表示它們是標準大小,並使用小端 (little-endian) 位元組順序:

import struct

with open('myfile.zip', 'rb') as f:
    data = f.read()

start = 0
for i in range(3):                      # 顯示前 3 個檔案標頭
    start += 14
    fields = struct.unpack('<IIIHH', data[start:start+16])
    crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

    start += 16
    filename = data[start:start+filenamesize]
    start += filenamesize
    extra = data[start:start+extra_size]
    print(filename, hex(crc32), comp_size, uncomp_size)

    start += extra_size + comp_size     # 跳到下一個標頭

11.4. 多執行緒 (Multi-threading)

執行緒是一種用來對非順序相依 (sequentially dependent) 的多個任務進行解耦 (decoupling) 的技術。當其他任務在背景執行時,多執行緒可以用來提升應用程式在接收使用者輸入時的反應能力。一個相關的用例是,運行 I/O 的同時,在另一個執行緒中進行計算。

以下程式碼顯示了高階的 threading 模組如何在背景運行任務,而主程式同時繼續運行:

import threading, zipfile

class AsyncZip(threading.Thread):
    def __init__(self, infile, outfile):
        threading.Thread.__init__(self)
        self.infile = infile
        self.outfile = outfile

    def run(self):
        f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
        f.write(self.infile)
        f.close()
        print('Finished background zip of:', self.infile)

background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print('The main program continues to run in foreground.')

background.join()    # 等待背景任務結束
print('Main program waited until background was done.')

多執行緒應用程式的主要挑戰是,要協調多個彼此共享資料或資源的執行緒。為此,threading 模組提供了多個同步原語 (synchronization primitive),包括鎖 (lock)、事件 (event)、條件變數 (condition variable) 和號誌 (semaphore)。

儘管這些工具很強大,但很小的設計錯誤也可能導致一些難以重現的問題。所以,任務協調的首選方法是,把所有對資源的存取集中到單一的執行緒中,然後使用 queue 模組向該執行緒饋送來自其他執行緒的請求。應用程式若使用 Queue 物件進行執行緒間的通信和協調,會更易於設計、更易讀、更可靠。

11.5. 日誌記錄 (Logging)

logging 模組提供功能齊全且富彈性的日誌記錄系統。在最簡單的情況下,日誌訊息會被發送到檔案或 sys.stderr

import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf'