11. 标准库概览 — 第II部分

第二部分提供了更高级的模块用来支持专业编程的需要。这些模块很少出现在小型的脚本里。

11.1. 输出格式

reprlib模块提供一个定制版的repr()用于显示大型或者深层嵌套容器:

>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"set(['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模块会访问区域性特定数据格式的数据库。分组属性的区域设置的格式函数的格式设置的数字以直接的方式提供了组分隔符:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv()          # get a mapping of conventions
>>> x = 1234567.8
>>> locale.format("%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. 模板

string模块包括一个通用Template类,它用简化的语法适合最终用户编辑。这允许用户自定义他们的应用程序无需修改应用程序。

这种格式使用的占位符名称由$与有效的 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.'

当字典或关键字参数中没有提供占位符时,substitute()方法将引发KeyError。对于邮件-合并风格的应用程序,用户提供的数据可能不完整,这时safe_substitute()方法可能会更合适 —— 如果没有数据它将保持占位符不变:

>>> 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 类的子类可以指定自定义的分隔符。例如,图像浏览器的批量命名工具可能选用百分号作为表示当前日期、图像 序列号或文件格式的占位符:

>>> 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. 二进制数据记录格式

The struct module provides pack() and unpack() functions for working with variable length binary record formats. The following example shows how to loop through header information in a ZIP file without using the zipfile module. Pack codes "H" and "I" represent two and four byte unsigned numbers respectively. The "<" indicates that they are standard size and in little-endian byte order:

import struct

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

start = 0
for i in range(3):                      # show the first 3 file headers
    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     # skip to the next header

11.4. 多线程

线程是一种解耦非顺序依赖任务的技术。线程可以用来提高接应用程序受用户输入的响应速度,而其他任务同时在后台运行。一个相关的使用场景是 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()    # Wait for the background task to finish
print('Main program waited until background was done.')

多线程应用程序的最主要挑战是协调线程间共享的数据或其他资源。为此目的,该线程模块提供了许多同步原语包括锁、 事件、 条件变量和信号量。

尽管这些工具很强大,很小的设计错误也可能导致很难复现的问题。因此,任务协调的首选方法是把对一个资源的所有访问集中在一个单独的线程中,然后使用queue模块用那个线程服务其他线程的请求。应用程序使用Queue对象进行线程间的通信和协调将更容易设计、 更具可读性和更可靠。

11.5. 日志

logging模块提供了一个具有完整功能并且非常灵活的日志系统。最简单的,发送消息到一个文件或者sys.stderr:

import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')

这将生成以下输出:

WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down

默认情况下,信息和调试消息被压制并输出到标准错误。其他输出选项包括将消息通过email、 datagrams、sockets发送,或者发送到 HTTP 服务器。根据消息的优先级,新的过滤器可以选择不同的方式:DEBUG、INFO、WARNING、ERROR和CRITICAL。

日志系统可以直接在 Python 代码中定制,也可以不经过应用程序直接在一个用户可编辑的配置文件中加载。

11.6. 弱引用

Python 会自动进行内存管理 (对大多数的对象进行引用计数和垃圾回收以循环利用)。在最后一个引用消失后,内存会立即释放。

这个方式对大多数应用程序工作良好,但是有时候会需要跟踪对象,只要它们还被其它地方所使用。不幸的是,只是跟踪它们也会创建一个引用,这将使它们永久保留。weakref模块提供工具用来无需创建一个引用跟踪对象。当不再需要该对象时,它会自动从 weakref 表中删除并且会为 weakref 对象触发一个回调。典型的应用包括缓存创建的时候需要很大开销的对象:

>>> import weakref, gc
>>> class A:
...     def __init__(self, value):
...         self.value = value
...     def __repr__(self):
...         return str(self.value)
...
>>> a = A(10)                   # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a            # does not create a reference
>>> d['primary']                # fetch the object if it is still alive
10
>>> del a                       # remove the one reference
>>> gc.collect()                # run garbage collection right away
0
>>> d['primary']                # entry was automatically removed
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    d['primary']                # entry was automatically removed
  File "C:/python34/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

11.7. 列表工具

很多数据结构使用内置列表类型就可以满足需求。然而,有时需要其它具有不同性能的替代实现。

The array module provides an array() object that is like a list that stores only homogeneous data and stores it more compactly. The following example shows an array of numbers stored as two byte unsigned binary numbers (typecode "H") rather than the usual 16 bytes per entry for regular lists of Python int objects:

>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])

collections模块提供了一个deque()对象,就像一个列表,不过它从左边添加和弹出更快,但是在内部查询更慢。这些对象非常实现队列和广度优先的树搜索:

>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Handling", d.popleft())
Handling task1
unsearched = deque([starting_node])
def breadth_first_search(unsearched):
    node = unsearched.popleft()
    for m in gen_moves(node):
        if is_goal(m):
            return m
        unsearched.append(m)

除了列表的替代实现,该库还提供了其它工具例如bisect模块中包含处理排好序的列表的函数:

>>> import bisect
>>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(scores, (300, 'ruby'))
>>> scores
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

heapq模块提供的函数可以实现基于常规列表的堆。最小的值总是保持在第零个位置。这对循环访问最小元素,但是不想运行完整列表排序的应用非常有用:

>>> from heapq import heapify, heappop, heappush
>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data)                      # rearrange the list into heap order
>>> heappush(data, -5)                 # add a new entry
>>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
[-5, 0, 1]

11.8. 十进制浮点数运算

decimal模块提供一个Decimal数据类型用于为十进制浮点运算。相比二进制浮点数内置的float实现,这个类对于以下情形特别有用:

  • 财务应用程序和其他用途,需要精确的十进制表示形式,
  • 控制精度,
  • 对符合法律或法规要求,舍入的控制
  • 跟踪有效小数位
  • 用户希望计算结果与手工计算相符的应用程序。

例如,计算上 70%电话费的 5%税给不同的十进制浮点和二进制浮点结果。区别变得明显如果结果舍入到最接近的分:

>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73

Decimal的结果总是保有结尾的0,自动从两位精度延伸到4位。Decimal 类似手工完成的数学运算,这就避免了二进制浮点数无法精确表达数据精度产生的问题。

精确地表示允许Decimal可以执行二进制浮点数无法进行的模运算和等值测试:

>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False

decimal模块提供任意精度的运算:

>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')