11 标准库简介 - 第二部分

    第二部分的浏览包含更多的高级模块,可以支持专业的编程需要。这些模块很少出现在小段的脚本中。

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模块可以访问记录了不同文化下数据格式差异的数据库。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 模板(Templating)

    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。在mail-merge风格的应用中,用户提供的数据可能不全,这是使用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 使用二进制数据记录布局

    struct模块提供了pack()unpack()函数,用来处理不定长二进制记录的格式化。下面的例子展示了如何不使用zipfile模块遍历Zip文件头信息。打包代码中"H""I"分别代表2byte和4byte的无符号数字。"<"符号代表它们使用标准大大小并且使用小端字节序:

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

    多线程应用程序最重要的挑战是,协调线程间的共享数据或其他资源。为此,threading模块提供了许多原语,包括锁(locks)、事件(events)、条件变量(condition variables)、信号量(semaphores)。

    虽然那些工具很强大,但是微小的设计错误可能导致难以复现的问题。所以,协调任务的首选方案是将所有资源访问都集中在一个线程中,然后使用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

    默认情况下,信息和调试消息会被抑制,并且它们是被发送到标准错误流中输出。其他的输出选项包括经由邮件、数据报、套接字的路由消息或者是发送给一个HTTP服务器。新的过滤器可以根据消息的优先级选择不同的路由,优先级有:DEBUG、INFO、WARNING,、ERROR和CRITICAL。

    日志系统可以在不改变应用程序的情况下,直接通过Python进行配置,也可以加载一个用户可编辑的配置文件来定制日志内容。

11.6 弱引用(Weak References)

    Python会自动进行内存管理(为大多数对象进行引用计数,周期性进行垃圾回收)。在最后一个引用消失后不久内存就会被释放。

    这种机制适用于大多数应用程序, 但也有偶然情况, 只有当它们被其它东西使用时才需要去跟踪对象。不幸的是,仅是要追踪它们也需要创建的一个引用,这会使它们永久存在(翻译的可能有误,原文:just tracking them creates a reference that makes them permanent)。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 使用Lists的工具

    对很多数据结构的需求都可使用内建的list类型来满足。然而,有时候需要在不同的性能权衡中选择一种实现方式。

    array模块提供了一个array()对象,就像一个list,可以存储同一类型的数据并且更紧凑(这里应该是指内存开销小)。下面这个例子演示了将一组数字以2byte的无符号二进制数(类型码 "H") 形式存储为一个array,而不是常规的list存储的每项16byte的Python整数对象(原文: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()对象,和list类似,但是它在左侧的append、pop操作速度快,在中间的查找很慢。这些对象适合实现队列和广度优先树的查找:

>>> 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模块提供了一些基于常规list实现堆的函数。输入的最小的值总是保持在位置0处。这对于重复访问最小的元素但又不想对整个列表进行排序的应用程序来说十分有用:

>>> 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,并会从两位有效数字的数乘中自动推导出思维有效数字。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')