11. 标准库简介 — 第二部分 — Python 文档

来自菜鸟教程
Python/docs/3.9/tutorial/stdlib2
跳转至:导航、​搜索

11. 标准库简介——第二部分

第二次导览涵盖了支持专业编程需求的更高级模块。 这些模块很少出现在小脚本中。

11.1. 输出格式

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 属性提供了一种使用组分隔符格式化数字的直接方法:

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

模板子类可以指定自定义分隔符。 例如,照片浏览器的批量重命名实用程序可以选择使用百分号作为占位符,例如当前日期、图像序列号或文件格式:

>>> 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 Web 报告成为可能。


11.3. 使用二进制数据记录布局

struct 模块提供了 pack()unpack() 函数,用于处理可变长度的二进制记录格式。 以下示例显示了如何在不使用 zipfile 模块的情况下遍历 ZIP 文件中的标头信息。 包码"H""I"分别代表二字节和四字节无符号数。 "<" 表示它们是标准大小和小端字节序:

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

默认情况下,信息和调试消息被抑制,输出被发送到标准错误。 其他输出选项包括通过电子邮件、数据报、套接字或到 HTTP 服务器路由消息。 新过滤器可以根据消息优先级选择不同的路由:DEBUGINFOWARNINGERRORCRITICAL

日志系统可以直接从 Python 配置,也可以从用户可编辑的配置文件中加载,用于自定义日志记录,而无需更改应用程序。


11.6. 弱引用

Python 执行自动内存管理(大多数对象的引用计数和 垃圾收集 以消除循环)。 内存在最后一次引用被消除后不久被释放。

这种方法适用于大多数应用程序,但偶尔需要跟踪对象,只要它们被其他东西使用。 不幸的是,只是跟踪它们会创建一个使它们永久的参考。 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:/python39/lib/weakref.py", line 46, in __getitem__
    o = self.data[key]()
KeyError: 'primary'

11.7. 处理列表的工具

内置列表类型可以满足许多数据结构需求。 但是,有时需要具有不同性能权衡的替代实现。

array 模块提供了一个 array() 对象,它就像一个列表,只存储同构数据,存储更紧凑。 以下示例显示了一个存储为两个字节无符号二进制数(类型代码 "H")的数字数组,而不是 Python int 对象的常规列表中每个条目通常的 16 个字节:

>>> 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 结果保持尾随零,自动从具有两位重要性的被乘数推断出四位重要性。 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')