Python3学习 # 第十章 开箱即用

#!usr/bin/env python  
# -*- coding:utf-8 _*-
""" 
@author:AIVision 
@file: lecture10.py 
@time: 2023/08/28 
"""
# 第十章 开箱即用

# 至此,你掌握了Python语言的大部分基础知识。Python不仅语言核心非常强大,还提供了其
# 他工具以供使用。标准安装包含一组称为标准库(standard library)的模块,你见过其中的一些
# (如math和cmath),但还有其他很多。本章简要介绍模块的工作原理以及如何探索模块以获悉其
# 提供的功能,然后概述标准库,重点是几个很有用的模块。

# 10.1 模块
# 你已知道如何创建和执行程序(或脚本),还知道如何使用import将函数从外部模块导入到
# 程序中。
# >>> import math
# >>> math.sin(0)
# 0.0
# 下面来看看如何编写自己的模块。

# 10.1.1 模块就是程序
# 任何Python程序都可作为模块导入。假设你编写了代码清单10-1所示的程序,并将其保存在
# 文件hello.py中,这个文件的名称(不包括扩展名.py)将成为模块的名称。

# 代码清单10-1 一个简单的模块
# hello.py
print("Hello, world!")

# 文件的存储位置也很重要,将在下一节详细介绍。这里假设这个文件存储在目录C:\python
# (Windows)或~/python(UNIX/macOS)中。
# 要告诉解释器去哪里查找这个模块,可执行如下命令(以Windows目录为例):

# >>> import sys
# >>> sys.path.append('C:/python')

# 提示 在UNIX中,不能直接将字符串'~/python'附加到sys.path末尾,而必须使用完整的路径
# (如'/home/yourusername/python')。如果你要自动创建完整的路径,可使用sys.path.
# expanduser('~/python')。

# 这告诉解释器,除了通常将查找的位置外,还应到目录C:\python中去查找这个模块。这样做
# 后,就可以导入这个模块了(它存储在文件C:\python\hello.py中)。
# >>> import hello
# Hello, world!

# 注意 当你导入模块时,可能发现其所在目录中除源代码文件外,还新建了一个名为__pycache__
# 的子目录(在较旧的Python版本中,是扩展名为.pyc的文件)。这个目录包含处理后的文
# 件,Python能够更高效地处理它们。以后再导入这个模块时,如果.py文件未发生变化,
# Python将导入处理后的文件,否则将重新生成处理后的文件。删除目录__pycache__不会
# 有任何害处,因为必要时会重新创建它。

# 如你所见,导入这个模块时,执行了其中的代码。但如果再次导入它,什么事情都不会发生。
# >>> import hello
# >>>

# 这次为何没有执行代码呢?因为模块并不是用来执行操作(如打印文本)的,而是用于定义
# 变量、函数、类等。鉴于定义只需做一次,因此导入模块多次和导入一次的效果相同。

# 10.1.2 模块是用来下定义的
# 模块在首次被导入程序时执行。这看似有点用,但用处不大。让模块值得被创建的原因在于
# 它们像类一样,有自己的作用域。这意味着在模块中定义的类和函数以及对其进行赋值的变量都
# 将成为模块的属性。这看似复杂,但实际上非常简单。
# 1. 在模块中定义函数
# 假设你编写了一个类似于代码清单10-2所示的模块,并将其存储在文件hello2.py中。另外,
# 假设你将这个文件放在了Python解释器能够找到的地方(可像前一节介绍的那样使用sys.path,
# 也可使用10.1.3节介绍的传统方式)。

# 提示 像处理模块那样,让程序(这意味着将被执行,而不是用作模块)可用后,可使用Python
# 解释器开关-m来执行它。如果随其他模块一起安装了文件progname.py(请注意扩展名),
# 即导入了progname,命令python -m progname args将使用命令行参数args来执行程序progname。

# 代码清单10-2 只包含一个函数的简单模块
# # hello2.py
# def hello():
#  print("Hello, world!")
# 现在可以像下面这样导入它:
# >>> import hello2
# 这将执行这个模块,也就是在这个模块的作用域内定义函数hello,因此可像下面这样访问
# 这个函数:
# >>> hello2.hello()
# Hello, world!
# 在模块的全局作用域内定义的名称都可像上面这样访问。为何要这样做呢?为何不在主程序
# 中定义一切呢?
# 主要是为了重用代码。通过将代码放在模块中,就可在多个程序中使用它们。这意味着如果
# 你编写了一个出色的客户数据库,并将其放在模块clientdb中,就可在记账时、发送垃圾邮件(但
# 愿你不会这样做)时以及任何需要访问客户数据的程序中使用它。如果没有放在独立的模块中,
# 就需在每个这样的程序中重新编写它。因此,要让代码是可重用的,务必将其模块化!(这也与
# 抽象紧密相关。)
# 2. 在模块中添加测试代码
# 模块用于定义函数和类等,但在有些情况下(实际上是经常),添加一些测试代码来检查情
# 况是否符合预期很有用。例如,如果要确认函数hello管用,你可能将模块hello2重写为代码清
# 单10-3所示的模块hello3。

# 代码清单10-3 一个简单的模块,其中的测试代码有问题
# # hello3.py
# def hello():
#  print("Hello, world!")
# # 一个测试:
# hello()
# 这看似合理:如果将这个模块作为普通程序运行,将发现它运行正常。然而,如果在另一个
# 程序中将其作为模块导入,以便能够使用函数hello,也将执行测试代码,就像本章的第一个hello模块一样。

# >>> import hello3
# Hello, world!
# >>> hello3.hello()
# Hello, world!

# 这不是你想要的结果。要避免这种行为,关键是检查模块是作为程序运行还是被导入另一个
# 程序。为此,需要使用变量__name__。
# >>> __name__
# '__main__'
# >>> hello3.__name__
# 'hello3'
# 如你所见,在主程序中(包括解释器的交互式提示符),变量__name__的值是'__main__',而
# 在导入的模块中,这个变量被设置为该模块的名称。因此,要让模块中测试代码的行为更合理,
# 可将其放在一条if语句中,如代码清单10-4所示。

# 代码清单10-4 一个包含有条件地执行的测试代码的模块
# hello4.py
def hello():
    print("Hello, world!")
def test():
    hello()
if __name__ == '__main__':
    test()
# 如果将这个模块作为程序运行,将执行函数hello;如果导入它,其行为将像普通模块一样。
# >>> import hello4
# >>> hello4.hello()
# Hello, world!

# 如你所见,我将测试代码放在了函数test中。原本可以将这些代码直接放在if语句中,但通
# 过将其放在一个独立的测试函数中,可在程序中导入模块并对其进行测试。
# >>> hello4.test()
# Hello, world!

# 10.1.3 让模块可用
# 在前面的示例中,我修改了sys.path。sys.path包含一个目录(表示为字符串)列表,解释
# 器将在这些目录中查找模块。然而,通常你不想这样做。最理想的情况是,sys.path一开始就包
# 含正确的目录(你的模块所在的目录)。为此有两种办法:将模块放在正确的位置;告诉解释器
# 到哪里去查找。接下来的两节将分别讨论这两种解决方案。如果要让别人能够轻松地使用你的模
# 块,那就是另外一码事了。Python打包技术一度日益复杂、各自为政,尽管现已被Python Packaging
# Authority控制并简化,但需要学习的还是有很多。这里不深入介绍这个棘手的主题,建议参阅
# “Python打包用户指南”:packaging.python.org。

# 1. 将模块放在正确的位置
# 将模块放在正确的位置很容易,只需找出Python解释器到哪里去查找模块,再将文件放在这
# 个地方即可。在你使用的计算机中,如果Python解释器是管理员安装的,而你有没有管理员权限,
# 就可能无法将模块保存到Python使用的目录中。在这种情况下,需要采用随后要介绍的另一种解
# 决方案:告诉解释器去哪里查找。
# 你可能还记得,可在模块sys的变量path中找到目录列表(即搜索路径)。

# >>> import sys, pprint
# >>> pprint.pprint(sys.path)
# ['C:\\Python35\\Lib\\idlelib',
#  'C:\\Python35',
#  'C:\\Python35\\DLLs',
#  'C:\\Python35\\lib',
#  'C:\\Python35\\lib\\plat-win',
#  'C:\\Python35\\lib\\lib-tk',
#  'C:\\Python35\\lib\\site-packages']

# 提示 如果要打印的数据结构太大,一行容纳不下,可使用模块pprint中的函数pprint(而不是
# 普通print语句)。pprint是个卓越的打印函数,能够更妥善地打印输出。

# 当然,你得到的打印结果可能与这里显示的不完全相同。这里的要点是,每个字符串都表示
# 一个位置,如果要让解释器能够找到模块,可将其放在其中任何一个位置中。虽然放在这里显示
# 的任何一个位置中都可行,但目录site-packages是最佳的选择,因为它就是用来放置模块的。请
# 在你的计算机中查看sys.path,找到目录site-packages,并将代码清单10-4所示的模块保存到这里,
# 但要使用另一个名称,如another_hello.py。然后,尝试像下面这样做:
# >>> import another_hello
# >>> another_hello.hello()
# Hello, world!
# 只要模块位于类似于site-packages这样的地方,所有的程序就都能够导入它。

# 2. 告诉解释器到哪里去查找
# 将模块放在正确的位置可能不是合适的解决方案,其中的原因很多。
#  不希望Python解释器的目录中充斥着你编写的模块。
#  没有必要的权限,无法将文件保存到Python解释器的目录中。
#  想将模块放在其他地方。
# 最重要的是,如果将模块放在其他地方,就必须告诉解释器到哪里去查找。前面说过,要告
# 诉解释器到哪里去查找模块,办法之一是直接修改sys.path,但这种做法不常见。标准做法是将
# 模块所在的目录包含在环境变量PYTHONPATH中。
# 环境变量PYTHONPATH的内容随操作系统而异(参见旁注“环境变量”),但它基本上类似于
# sys.path,也是一个目录列表。

# 除使用环境变量PYTHONPATH外,还可使用路径配置文件。这些文件的扩展名为.pth,位于一
# 些特殊目录中,包含要添加到sys.path中的目录。有关这方面的详细信息,请参阅有关模块site的标准库文档。

# 10.1.4 包
# 为组织模块,可将其编组为包(package)。包其实就是另一种模块,但有趣的是它们可包含其
# 他模块。模块存储在扩展名为.py的文件中,而包则是一个目录。要被Python视为包,目录必须包含
# 文件__init__.py。如果像普通模块一样导入包,文件__init__.py的内容就将是包的内容。例如,如果
# 有一个名为constants的包,而文件constants/__init__.py包含语句PI = 3.14,就可以像下面这样做:
# import constants
# print(constants.PI)
# 要将模块加入包中,只需将模块文件放在包目录中即可。你还可以在包中嵌套其他包。例如,
# 要创建一个名为drawing的包,其中包含模块shapes和colors,需要创建如表10-1所示的文件和目
# 录(UNIX路径名)。

# 10.2 探索模块
# 介绍一些标准库模块前,先来说说如何探索模块。这是一种很有用的技能,因为在你的Python
# 程序员职业生涯中,将遇到很多很有用的模块,而这里无法一一介绍。当前的标准库很大,足以
# 编写专著来论述(市面上也确实有这样的专著),而且还在不断增大。每个新Python版本都新增
# 了模块,通常还会对一些既有模块进行细微的修改和改进。另外,你在网上肯定会找到一些很有
# 用的模块。如果能快速而轻松地理解它们,编程工作将有趣得多。

# 10.2.1 模块包含什么
# 要探索模块,最直接的方式是使用Python解释器进行研究。为此,首先需要将模块导入。假
# 设你听说有一个名为copy的标准模块。
# >>> import copy
# 没有引发异常,说明确实有这样的模块。但这个模块是做什么用的呢?它都包含些什么呢?

# 1. 使用dir
# 要查明模块包含哪些东西,可使用函数dir,它列出对象的所有属性(对于模块,它列出所
# 有的函数、类、变量等)。如果将dir(copy)的结果打印出来,将是一个很长的名称列表(请试试
# 看)。在这些名称中,有几个以下划线打头。根据约定,这意味着它们并非供外部使用。有鉴于
# 此,我们使用一个简单的列表推导将这些名称过滤掉(如果你忘记了列表推导的工作原理,请参
# 阅5.6节)。
# >>> [n for n in dir(copy) if not n.startswith('_')]
# ['Error', 'PyStringMap', 'copy', 'deepcopy', 'dispatch_table', 'error', 'name', 't', 'weakref']
# 结果包含dir(copy)返回的不以下划线打头的名称,这比完整清单要好懂些。

# 2. 变量__all__
# 在前一节中,我使用简单的列表推导来猜测可在模块copy中看到哪些内容,然而可直接咨询
# 这个模块来获得正确的答案。你可能注意到了,在dir(copy)返回的完整清单中,包含名称__all__。
# 这个变量包含一个列表,它与前面使用列表推导创建的列表类似,但是在模块内部设置的。下面
# 来看看这个列表包含的内容:
# >>> copy.__all__
# ['Error', 'copy', 'deepcopy']
# 前面的猜测不算太离谱,只是多了几个并非供用户使用的名称。这个__all__列表是怎么来
# 的呢?为何要提供它?第一个问题很容易回答:它是在模块copy中像下面这样设置的(这些代码
# 是直接从copy.py复制而来的):
# __all__ = ["Error", "copy", "deepcopy"]
# 为何要提供它呢?旨在定义模块的公有接口。具体地说,它告诉解释器从这个模块导入所有
# 的名称意味着什么。因此,如果你使用如下代码:
# from copy import *
# 将只能得到变量__all__中列出的4个函数。要导入PyStringMap,必须显式地:导入copy并使用
# copy.PyStringMap;或者使用from copy import PyStringMap。
# 编写模块时,像这样设置__all__也很有用。因为模块可能包含大量其他程序不需要的变量、
# 函数和类,比较周全的做法是将它们过滤掉。如果不设置__all__,则会在以import *方式导入时,
# 导入所有不以下划线打头的全局名称。

# 10.2.2 使用 help 获取帮助
# 前面一直在巧妙地利用你熟悉的各种Python函数和特殊属性来探索模块copy。对这种探索来
# 说,交互式解释器是一个强大的工具,因为使用它来探测模块时,探测的深度仅受限于你对Python
# 语言的掌握程度。然而,有一个标准函数可提供你通常需要的所有信息,它就是help。下面来尝
# 试使用它获取有关函数copy的信息:
# >>> help(copy.copy)
# Help on function copy in module copy:
# copy(x)
#  Shallow copy operation on arbitrary Python objects.
#  See the module's __doc__ string for more info.
# 上述帮助信息指出,函数copy只接受一个参数x,且执行的是浅复制。在帮助信息中,还提
# 到了模块的__doc__字符串。__doc__字符串是什么呢?你可能还记得,第6章提到了文档字符串。
# 文档字符串就是在函数开头编写的字符串,用于对函数进行说明,而函数的属性__doc__可能包
# 含这个字符串。从前面的帮助信息可知,模块也可能有文档字符串(它们位于模块的开头),而
# 类也可能如此(位于类的开头)。
# 实际上,前面的帮助信息是从函数copy的文档字符串中提取的:
# >>> print(copy.copy.__doc__)
# Shallow copy operation on arbitrary Python objects.
#  See the module's __doc__ string for more info.

# 相比于直接查看文档字符串,使用help的优点是可获取更多的信息,如函数的特征标(即它
# 接受的参数)。请尝试对模块copy本身调用help,看看将显示哪些信息。这将打印大量的信息,
# 包括对copy和deepcopy之间差别的详细讨论(大致而言,deepcopy(x)创建x的属性的副本并依此
# 类推;而copy(x)只复制x,并将副本的属性关联到x的属性值)。

# 10.2.3 文档
# 显然,文档是有关模块信息的自然来源。我之所以到现在才讨论文档,是因为查看模块本身
# 要快得多。例如,你可能想知道range的参数是什么?在这种情况下,与其在Python图书或标准
# Python文档中查找对range的描述,不如直接检查这个函数。
# >>> print(range.__doc__)
# range(stop) -> range object
# range(start, stop[, step]) -> range object
# Return an object that produces a sequence of integers from start (inclusive)
# to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
# start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
# These are exactly the valid indices for a list of 4 elements.
# When step is given, it specifies the increment (or decrement).
# 这样就获得了函数range的准确描述。另外,由于通常是在编程时想了解函数的功能,而此
# 时Python解释器很可能正在运行,因此获取这些信息只需几秒钟。
# 然而,并非每个模块和函数都有详尽的文档字符串(虽然应该如此),且有时需要有关工作
# 原理的更详尽描述。从网上下载的大多数模块都有配套文档。就学习Python编程而言,最有用的
# 文档是“Python库参考手册”,它描述了标准库中的所有模块。在需要获悉一些有关Python的事实
# 时,十有八九能在这里找到。“Python库参考手册”(https://docs.python.org/library)可在线浏览和
# 下载,几个其他的标准文档(如“Python 入门指南”和“Python 语言参考手册”)也是如此。所
# 有的文档都可在Python网站(https://docs.python.org)上找到。

# 10.2.4 使用源代码
# 在大多数情况下,前面讨论的探索技巧都够用了。但要真正理解Python语言,可能需要了解
# 一些不阅读源代码就无法了解的事情。事实上,要学习Python,阅读源代码是除动手编写代码外
# 的最佳方式。
# 实际阅读源代码应该不成问题,但源代码在哪里呢?假设你要阅读标准模块copy的代码,可
# 以在什么地方找到呢?一种办法是像解释器那样通过sys.path来查找,但更快捷的方式是查看模
# 块的特性__file__。
# >>> print(copy.__file__)
# C:\Python35\lib\copy.py
# 找到了!你可在代码编辑器(如IDLE)中打开文件copy.py,并开始研究其工作原理。如果
# 列出的文件名以.pyc结尾,可打开以.py结尾的相应文件。

# 警告 在文本编辑器中打开标准库文件时,存在不小心修改它的风险。这可能会破坏文件。因
# 此关闭文件时,千万不要保存你可能对其所做的修改。

# 请注意,有些模块的源代码你完全无法读懂。它们可能是解释器的组成部分(如模块sys),
# 还可能是使用C语言编写的①。(有关如何使用C语言扩展Python的详细信息,请参阅第17章。)

# 10.3 标准库:一些深受欢迎的模块
# 在Python中,短语“开箱即用”(batteries included)最初是由Frank Stajano提出的,指的是
# Python丰富的标准库。安装Python后,你就免费获得了大量很有用的模块。鉴于有很多方式可以
# 获取有关这些模块的详细信息(本章前面介绍过),这里不打算提供完整的参考手册(如果这样
# 做将占据很大的篇幅),而只是描述几个我喜欢的标准模块,以激发你的探索兴趣。在本书后面
# 介绍项目的章节(第20章~第29章)中,你将遇到其他的标准模块。这里对模块的描述并非面面
# 俱到,只是将重点放在模块的一些有趣功能上。

# 10.3.1 sys
# 模块sys让你能够访问与Python解释器紧密相关的变量和函数,表10-2列出了其中的一些。

# 表10-2 模块sys中一些重要的函数和变量
# 函数/变量             描 述
# argv              命令行参数,包括脚本名
# exit([arg])       退出当前程序,可通过可选参数指定返回值或错误消息
# modules           一个字典,将模块名映射到加载的模块
# path              一个列表,包含要在其中查找模块的目录的名称
# platform          一个平台标识符,如sunos5或win32
# stdin             标准输入流——一个类似于文件的对象
# stdout            标准输出流——一个类似于文件的对象
# stderr            标准错误流——一个类似于文件的对象

# 变量sys.argv包含传递给Python解释器的参数,其中包括脚本名。
# 函数sys.exit退出当前程序。(在第8章讨论的try/finally块中调用它时,finally子句依然会
# 执行。)你可向它提供一个整数,指出程序是否成功,这是一种UNIX约定。在大多数情况下,使
# 用该参数的默认值(0,表示成功)即可。也可向它提供一个字符串,这个字符串将成为错误消
# 息,对用户找出程序终止的原因很有帮助。在这种情况下,程序退出时将显示指定的错误消息以
# 及一个表示失败的编码。
# 映射sys.modules将模块名映射到模块(仅限于当前已导入的模块)。
# 变量sys.path在本章前面讨论过,它是一个字符串列表,其中的每个字符串都是一个目录名,
# 执行import语句时将在这些目录中查找模块。
# 变量sys.platform(一个字符串)是运行解释器的“平台”名称。这可能是表示操作系统的名
# 称(如sunos5或win32),也可能是表示其他平台类型(如Java虚拟机)的名称(如java1.4.0)——
# 如果你运行的是Jython。
# 变量sys.stdin、sys.stdout和sys.stderr是类似于文件的流对象,表示标准的UNIX概念:
# 标准输入、标准输出和标准错误。简单地说,Python从sys.stdin获取输入(例如,用于input中),
# 并将输出打印到sys.stdout。有关文件和这三个流的详细信息,请参阅第11章。
# 举个例子,来看看按相反顺序打印参数的问题。从命令行调用Python脚本时,你可能指定一
# 些参数,也就是所谓的命令行参数。这些参数将放在列表sys.argv中,其中sys.argv[0]为Python
# 脚本名。按相反的顺序打印这些参数非常容易,如代码清单10-5所示。

# 代码清单10-5 反转并打印命令行参数
# reverseargs.py
import sys
args = sys.argv[1:]
args.reverse()
print(' '.join(args))

# 如你所见,我创建了一个sys.argv的副本。也可修改sys.argv,但一般而言,不这样做更安
# 全,因为程序的其他部分可能依赖于包含原始参数的sys.argv。另外,注意到我跳过了sys.argv
# 的第一个元素,即脚本的名称。我使用args.reverse()反转这个列表,但不能打印这个操作的返
# 回值,因为它就地修改列表并返回None。下面是另一种解决方案:
# print(' '.join(reversed(sys.argv[1:])))
# 最后,为美化输出,我使用了字符串的方法join。下面来尝试运行这个程序(假设使用的是
# bash shell)。
# $ python reverseargs.py this is a test
# test a is this

# 10.3.2 os
# 模块os让你能够访问多个操作系统服务。它包含的内容很多,表10-3只描述了其中几个最有
# 用的函数和变量。除此之外,os及其子模块os.path还包含多个查看、创建和删除目录及文件的
# 函数,以及一些操作路径的函数(例如,os.path.split和os.path.join让你在大多数情况下都可
# 忽略os.pathsep)。有关这个模块的详细信息,请参阅标准库文档。在标准库文档中,还可找到
# 有关模块pathlib的描述,它提供了一个面向对象的路径操作接口。

# 表10-3  模块os中一些重要的函数和变量
# 函数/变量                 描 述
# environ               包含环境变量的映射
# system(command)       在子shell中执行操作系统命令
# sep                   路径中使用的分隔符
# pathsep               分隔不同路径的分隔符
# linesep               行分隔符('\n'、'\r'或'\r\n')
# urandom(n)            返回n个字节的强加密随机数据

# 映射os.environ包含本章前面介绍的环境变量。例如,要访问环境变量PYTHONPATH,可使用表达
# 式os.environ['PYTHONPATH']。这个映射也可用于修改环境变量,但并非所有的平台都支持这样做。
# 函数os.system用于运行外部程序。还有其他用于执行外部程序的函数,如execv和popen。前
# 者退出Python解释器,并将控制权交给被执行的程序,而后者创建一个到程序的连接(这个连接
# 类似于文件)。

# 10.3.3 fileinput
# 第11章将深入介绍如何读写文件,这里先来预演一下。模块fileinput让你能够轻松地迭代
# 一系列文本文件中的所有行。如果你这样调用脚本(假设是在UNIX命令行中):
# $ python some_script.py file1.txt file2.txt file3.txt
# 就能够依次迭代文件file1.txt到file3.txt中的行。你还可在UNIX管道中对使用UNIX标准命令
# cat提供给标准输入(sys.stdin)的行进行迭代。
# $ cat file.txt | python some_script.py
# 如果使用模块fileinput,在UNIX管道中使用cat调用脚本的效果将与以命令行参数的方式
# 向脚本提供文件名一样。表10-4描述了模块fileinput中最重要的函数。

# 表10-4 模块fileinput中一些重要的函数
# 函 数                                               描 述
# input([files[, inplace[, backup]]])             帮助迭代多个输入流中的行
# filename()                                      返回当前文件的名称
# lineno()                                        返回(累计的)当前行号
# filelineno()                                    返回在当前文件中的行号
# isfirstline()                                   检查当前行是否是文件中的第一行
# isstdin()                                       检查最后一行是否来自sys.stdin
# nextfile()                                      关闭当前文件并移到下一个文件
# close()                                         关闭序列

# 来看一个fileinput使用示例。假设你编写了一个Python脚本,并想给其中的代码行加上行号。
# 鉴于你希望这样处理后程序依然能够正常运行,因此必须在每行末尾以注释的方式添加行号。为
# 让这些行号对齐,可使用字符串格式设置功能。假设只允许每行代码最多包含40个字符,并在第
# 41个字符处开始添加注释。代码清单10-6演示了一种使用模块fileinput和参数inplace来完成这
# 种任务的简单方式。

# 代码清单10-6 在Python脚本中添加行号
# # numberlines.py
# import fileinput
# for line in fileinput.input(inplace=True):
#  line = line.rstrip()
#  num = fileinput.lineno()
#  print('{:<50} # {:2d}'.format(line, num))
# 如果像下面这样运行这个程序,并将其作为参数传入:
# $ python numberlines.py numberlines.py
# 这个程序将变成代码清单10-7那样。注意到程序本身被修改了,如果像上面这样运行它多次,
# 每行都将包含多个行号。本书前面介绍过,rstrip是一个字符串方法,它将删除指定字符串两端
# 的空白,并返回结果(参见3.4节以及附录B的表B-6)。

# 代码清单10-7 添加行号后的行号添加程序
# # numberlines.py # 1
#                  # 2
# import fileinput # 3
#                  # 4
# for line in fileinput.input(inplace=True): # 5
#  line = line.rstrip() # 6
#  num = fileinput.lineno() # 7
#  print('{:<50} # {:2d}'.format(line, num)) # 8

# 警告 务必慎用参数inplace,因为这很容易破坏文件。你应在不设置inplace的情况下仔细测试
# 程序(这样将只打印结果),确保程序能够正确运行后再让它修改文件。

# 10.3.4 集合、堆和双端队列
# 有用的数据结构有很多。Python支持一些较常用的,其中的字典(散列表)和列表(动态数
# 组)是Python语言的有机组成部分。还有一些虽然不那么重要,但有时也能派上用场。
# 1. 集合
# 很久以前,集合是由模块sets中的Set类实现的。虽然在既有代码中可能遇到Set实例,但除
# 非要向后兼容,否则真的没有理由再使用它。在较新的版本中,集合是由内置类set实现的,这
# 意味着你可直接创建集合,而无需导入模块sets。
# >>> set(range(10))
# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

# 可使用序列(或其他可迭代对象)来创建集合,也可使用花括号显式地指定。请注意,不能
# 仅使用花括号来创建空集合,因为这将创建一个空字典。
# >>> type({})
# <class 'dict'>

# 相反,必须在不提供任何参数的情况下调用set。集合主要用于成员资格检查,因此将忽略
# 重复的元素:
# >>> {0, 1, 2, 3, 0, 1, 2, 3, 4, 5}
# {0, 1, 2, 3, 4, 5}
# 与字典一样,集合中元素的排列顺序是不确定的,因此不能依赖于这一点。
# >>> {'fee', 'fie', 'foe'}
# {'foe', 'fee', 'fie'}
# 除成员资格检查外,还可执行各种标准集合操作(你可能在数学课上学过),如并集和交集,
# 为此可使用对整数执行按位操作的运算符(参见附录B)。例如,要计算两个集合的并集,可对其
# 中一个集合调用方法union,也可使用按位或运算符|。

# >>> a = {1, 2, 3}
# >>> b = {2, 3, 4}
# >>> a.union(b)
# {1, 2, 3, 4}
# >>> a | b
# {1, 2, 3, 4}

# 还有其他一些方法和对应的运算符,这些方法的名称清楚地指出了其功能:
# >>> c = a & b
# >>> c.issubset(a)
# True
# >>> c <= a
# True
# >>> c.issuperset(a)
# False
# >>> c >= a
# False
# >>> a.intersection(b)
# {2, 3}
# >>> a & b
# {2, 3}
# >>> a.difference(b)
# {1}
# >>> a - b
# {1}
# >>> a.symmetric_difference(b)
# {1, 4}
# >>> a ^ b
# {1, 4}
# >>> a.copy()
# {1, 2, 3}
# >>> a.copy() is a
# False

# 另外,还有对应于各种就地操作的方法以及基本方法add和remove。有关这些方法的详细信
# 息,请参阅“Python库参考手册”中讨论集合类型的部分。

# 提示 需要计算两个集合的并集的函数时,可使用set中方法union的未关联版本。这可能很有
# 用,如与reduce一起使用。
# >>> my_sets = []
# >>> for i in range(10):
# ... my_sets.append(set(range(i, i+5)))
# ...
# >>> reduce(set.union, my_sets)
# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}

# 集合是可变的,因此不能用作字典中的键。另一个问题是,集合只能包含不可变(可散列)
# 的值,因此不能包含其他集合。由于在现实世界中经常会遇到集合的集合,因此这可能是个问题。
# 所幸还有frozenset类型,它表示不可变(可散列)的集合。

# 2. 堆
# 另一种著名的数据结构是堆(heap),它是一种优先队列。优先队列让你能够以任意顺序添
# 加对象,并随时(可能是在两次添加对象之间)找出(并删除)最小的元素。相比于列表方法min,
# 这样做的效率要高得多。
# 实际上,Python没有独立的堆类型,而只有一个包含一些堆操作函数的模块。这个模块名为
# heapq(其中的q表示队列),它包含6个函数(如表10-5所示),其中前4个与堆操作直接相关。必
# 须使用列表来表示堆对象本身。

# 表10-5 模块heapq中一些重要的函数
# 函 数                                   描 述
# heappush(heap, x)                 将x压入堆中
# heappop(heap)                     从堆中弹出最小的元素
# heapify(heap)                     让列表具备堆特征
# heapreplace(heap, x)              弹出最小的元素,并将x压入堆中
# nlargest(n, iter)                 返回iter中n个最大的元素
# nsmallest(n, iter)                返回iter中n个最小的元素

# 函数heappush用于在堆中添加一个元素。请注意,不能将它用于普通列表,而只能用于使用
# 各种堆函数创建的列表。原因是元素的顺序很重要(虽然元素的排列顺序看起来有点随意,并没
# 有严格地排序)。
# >>> from heapq import *
# >>> from random import shuffle
# >>> data = list(range(10))
# >>> shuffle(data)
# >>> heap = []
# >>> for n in data:
# ... heappush(heap, n)
# ...
# >>> heap
# [0, 1, 3, 6, 2, 8, 4, 7, 9, 5]
# >>> heappush(heap, 0.5)
# >>> heap
# [0, 0.5, 3, 6, 1, 8, 4, 7, 9, 5, 2]
# 元素的排列顺序并不像看起来那么随意。它们虽然不是严格排序的,但必须保证一点:位置
# i处的元素总是大于位置i // 2处的元素(反过来说就是小于位置2 * i和2 * i + 1处的元素)。
# 这是底层堆算法的基础,称为堆特征(heap property)。
# 函数heappop弹出最小的元素(总是位于索引0处),并确保剩余元素中最小的那个位于索引0
# 处(保持堆特征)。虽然弹出列表中第一个元素的效率通常不是很高,但这不是问题,因为heappop
# 会在幕后做些巧妙的移位操作。

# >>> heappop(heap)
# 0
# >>> heappop(heap)
# 0.5
# >>> heappop(heap)
# 1
# >>> heap
# [2, 5, 3, 6, 9, 8, 4, 7]
# 函数heapify通过执行尽可能少的移位操作将列表变成合法的堆(即具备堆特征)。如果你的
# 堆并不是使用heappush创建的,应在使用heappush和heappop之前使用这个函数。
# >>> heap = [5, 8, 0, 3, 6, 7, 9, 1, 4, 2]
# >>> heapify(heap)
# >>> heap
# [0, 1, 5, 3, 2, 7, 9, 8, 4, 6]
# 函数heapreplace用得没有其他函数那么多。它从堆中弹出最小的元素,再压入一个新元素。
# 相比于依次执行函数heappop和heappush,这个函数的效率更高。
# >>> heapreplace(heap, 0.5)
# 0
# >>> heap
# [0.5, 1, 5, 3, 2, 7, 9, 8, 4, 6]
# >>> heapreplace(heap, 10)
# 0.5
# >>> heap
# [1, 2, 5, 3, 6, 7, 9, 8, 4, 10]

# 至此,模块heapq中还有两个函数没有介绍:nlargest(n, iter)和nsmallest(n, iter),:分
# 别用于找出可迭代对象iter中最大和最小的n个元素。这种任务也可通过先排序(如使用函数
# sorted)再切片来完成,但堆算法的速度更快,使用的内存更少(而且使用起来也更容易)。

# 3. 双端队列(及其他集合)
# 在需要按添加元素的顺序进行删除时,双端队列很有用。在模块collections中,包含类型
# deque以及其他几个集合(collection)类型。
# 与集合(set)一样,双端队列也是从可迭代对象创建的,它包含多个很有用的方法。
# >>> from collections import deque
# >>> q = deque(range(5))
# >>> q.append(5)
# >>> q.appendleft(6)
# >>> q
# deque([6, 0, 1, 2, 3, 4, 5])
# >>> q.pop()
# 5
# >>> q.popleft()
# 6
# >>> q.rotate(3)
# >>> q
# deque([2, 3, 4, 0, 1])
# >>> q.rotate(-1)
# >>> q
# deque([3, 4, 0, 1, 2])
# 双端队列很有用,因为它支持在队首(左端)高效地附加和弹出元素,而使用列表无法这样做。
# 另外,还可高效地旋转元素(将元素向右或向左移,并在到达一端时环绕到另一端)。双端队列对
# 象还包含方法extend和extendleft,其中extend类似于相应的列表方法,而extendleft类似于
# appendleft。请注意,用于extendleft的可迭代对象中的元素将按相反的顺序出现在双端队列中。

# 10.3.5 time
# 模块time包含用于获取当前时间、操作时间和日期、从字符串中读取日期、将日期格式化为
# 字符串的函数。日期可表示为实数(从“新纪元”1月1日0时起过去的秒数。“新纪元”是一个随
# 平台而异的年份,在UNIX中为1970年),也可表示为包含9个整数的元组。表10-6解释了这些整
# 数。例如,元组(2008, 1, 21, 12, 2, 56, 0, 21, 0)表示2008年1月21日12时2分56秒。这一天是
# 星期一,2008年的第21天(不考虑夏令时)。

# 秒的取值范围为0~61,这考虑到了闰一秒和闰两秒的情况。夏令时数字是一个布尔值(True
# 或False),但如果你使用-1,那么mktime[将时间元组转换为时间戳(从新纪元开始后的秒数)
# 的函数]可能得到正确的值。表10-7描述了模块time中一些最重要的函数。

# 表10-7 模块time中一些重要的函数
# 函 数                               描 述
# asctime([tuple])              将时间元组转换为字符串
# localtime([secs])             将秒数转换为表示当地时间的日期元组
# mktime(tuple)                 将时间元组转换为当地时间
# sleep(secs)                   休眠(什么都不做)secs秒
# strptime(string[, format])    将字符串转换为时间元组
# time()                        当前时间(从新纪元开始后的秒数,以UTC为准)

# 函数time.asctime将当前时间转换为字符串,如下所示:
# >>> time.asctime()
# 'Mon Jul 18 14:06:07 2016'
# 如果不想使用当前时间,也可向它提供一个日期元组(如localtime创建的日期元组)。要设
# 置更复杂的格式,可使用函数strftime,标准文档对此做了介绍。
# 函数time.localtime将一个实数(从新纪元开始后的秒数)转换为日期元组(本地时间)。如
# 果要转换为国际标准时间,应使用gmtime。
# 函数time.mktime将日期元组转换为从新纪元后的秒数,这与localtime的功能相反。
# 函数time.sleep让解释器等待指定的秒数。
# 函数time.strptime将一个字符串(其格式与asctime所返回字符串的格式相同)转换为日期
# 元组。(可选参数format遵循的规则与strftime相同,详情请参阅标准文档。)
# 函数time.time返回当前的国际标准时间,以从新纪元开始的秒数表示。虽然新纪元随平台
# 而异,但可这样进行可靠的计时:存储事件(如函数调用)发生前后time的结果,再计算它们的
# 差。有关这些函数的使用示例,请参阅10.3.6节。
# 表10-7只列出了模块time的一部分函数。这个模块的大部分函数执行的任务都与本节介绍的
# 任务类似或相关。如果要完成这里介绍的函数无法执行的任务,请查看“Python库参考手册”中
# 介绍模块time的部分,在那里你很可能找到刚好能完成这种任务的函数。
# 另外,还有两个较新的与时间相关的模块:datetime和timeit。前者提供了日期和时间算术
# 支持,而后者可帮助你计算代码段的执行时间。“Python库参考手册”提供了有关这两个模块的
# 详细信息。另外,第16章将简要地讨论timeit。

# 10.3.6 random
# 模块random包含生成伪随机数的函数,有助于编写模拟程序或生成随机输出的程序。请注意,
# 虽然这些函数生成的数字好像是完全随机的,但它们背后的系统是可预测的。如果你要求真正的
# 随机(如用于加密或实现与安全相关的功能),应考虑使用模块os中的函数urandom。模块random
# 中的SystemRandom类基于的功能与urandom类似,可提供接近于真正随机的数据。
# 表10-8列出了这个模块中一些重要的函数。

# 表10-8 模块random中一些重要的函数
# 函 数                   描 述
# random()              返回一个0~1(含)的随机实数
# getrandbits(n)        以长整数方式返回n个随机的二进制位
# uniform(a, b)         返回一个a~b(含)的随机实数
# randrange([start], stop, [step])      从range(start, stop, step)中随机地选择一个数
# choice(seq)           从序列seq中随机地选择一个元素
# shuffle(seq[, random]) 就地打乱序列seq
# sample(seq, n)        从序列seq中随机地选择n个值不同的元素

# 函数random.random是最基本的随机函数之一,它返回一个0~1(含)的伪随机数。除非这正
# 是你需要的,否则可能应使用其他提供了额外功能的函数。函数random.getrandbits以一个整数
# 的方式返回指定数量的二进制位。
# 向函数random.uniform提供了两个数字参数a和b时,它返回一个a~b(含)的随机(均匀分布
# 的)实数。例如,如果你需要一个随机角度,可使用uniform(0, 360)。
# 函数random.randrange是生成随机整数的标准函数。为指定这个随机整数所在的范围,
# 你可像调用range那样给这个函数提供参数。例如,要生成一个1~10(含)的随机整数,可
# 使用randrange(1, 11)或randrange(10) + 1。要生成一个小于20的随机正奇数,可使用randrange(1,20, 2)。
# 函数random.choice从给定序列中随机(均匀)地选择一个元素。
# 函数random.shuffle随机地打乱一个可变序列中的元素,并确保每种可能的排列顺序出现的
# 概率相同。
# 函数random.sample从给定序列中随机(均匀)地选择指定数量的元素,并确保所选择元素
# 的值各不相同。

# 注意 编写与统计相关的程序时,可使用其他类似于uniform的函数,它们返回按各种分布随机
# 采集的数字,如贝塔分布、指数分布、高斯分布等。

# 来看几个使用模块random的示例。在这些示例中,我将使用前面介绍的模块time中的几个函
# 数。首先,获取表示时间段(2016年)上限和下限的实数。为此,可使用时间元组来表示日期(将
# 星期、儒略日和夏令时都设置为1,让Python去计算它们的正确值),并对这些元组调用mktime:
# from random import *
# from time import *
# date1 = (2016, 1, 1, 0, 0, 0, -1, -1, -1)
# time1 = mktime(date1)
# date2 = (2017, 1, 1, 0, 0, 0, -1, -1, -1)
# time2 = mktime(date2)
# 接下来,以均匀的方式生成一个位于该范围内(不包括上限)的随机数:
# >>> random_time = uniform(time1, time2)
# 然后,将这个数转换为易于理解的日期。
# >>> print(asctime(localtime(random_time)))
# Tue Aug 16 10:11:04 2016
# 在接下来的示例中,我们询问用户要掷多少个骰子、每个骰子有多少面。掷骰子的机制是使
# 用randrange和for循环实现的。
# from random import randrange
# num = int(input('How many dice? '))
# sides = int(input('How many sides per die? '))
# sum = 0
# for i in range(num): sum += randrange(sides) + 1
# print('The result is', sum)
# 如果将这些代码放在一个脚本文件中并运行它,将看到类似于下面的交互过程:
# How many dice? 3
# How many sides per die? 6
# The result is 10
# 现在假设你创建了一个文本文件,其中每行都包含一种运气情况(fortune),那么就可使用
# 前面介绍的模块fileinput将这些情况放到一个列表中,再随机地选择一种。
# # fortune.py
# import fileinput, random
# fortunes = list(fileinput.input())
# print random.choice(fortunes)
# 在UNIX和macOS中,可使用标准字典文件/usr/share/dict/words来测试这个程序,这将获得一
# 个随机的单词。
# $ python fortune.py /usr/share/dict/words
# dodge
# 来看最后一个示例。假设你要编写一个程序,在用户每次按回车键时都发给他一张牌。另外,
# 你还要确保发给用户的每张牌都不同。为此,首先创建“一副牌”,也就是一个字符串列表。
# >>> values = list(range(1, 11)) + 'Jack Queen King'.split()
# >>> suits = 'diamonds clubs hearts spades'.split()
# >>> deck = ['{} of {}'.format(v, s) for v in values for s in suits]
# 刚才创建的这副牌并不太适合玩游戏。我们来看看其中一些牌:
# >>> from pprint import pprint
# >>> pprint(deck[:12])
# ['1 of diamonds',
#  '1 of clubs',
#  '1 of hearts',
#  '1 of spades',
#  '2 of diamonds',
#  '2 of clubs',
#  '2 of hearts',
#  '2 of spades',
#  '3 of diamonds',
#  '3 of clubs',
#  '3 of hearts',
#  '3 of spades']
# 太有规律了,对吧?这个问题很容易修复。
# >>> from random import shuffle
# >>> shuffle(deck)
# >>> pprint(deck[:12])
# ['3 of spades',
#  '2 of diamonds',
#  '5 of diamonds',
#  '6 of spades',
#  '8 of diamonds',
#  '1 of clubs',
#  '5 of hearts',
#  'Queen of diamonds',
#  'Queen of hearts',
#  'King of hearts',
#  'Jack of diamonds',
#  'Queen of clubs']
# 请注意,这里只打印了开头12张牌,旨在节省篇幅。如果你愿意,完全可以自己查看整副牌。
# 最后,要让Python在用户每次按回车键时都给他发一张牌,直到牌发完为止,只需创建一个
# 简单的while循环。如果将创建整副牌的代码放在了一个程序文件中,那么只需在这个文件末尾
# 添加如下代码即可:
# while deck: input(deck.pop())
# 请注意,如果在交互式解释器中尝试运行这个while循环,那么每当你按回车键时都将打印
# 一个空字符串。这是因为input返回你输入的内容(什么都没有),然后这些内容将被打印出来。
# 在普通程序中,将忽略input返回的值。要在交互式解释器中也忽略input返回的值,只需将其赋
# 给一个你不会再理会的变量,并将这个变量命名为ignore。

# 10.3.7 shelve 和 json
# 下一章将介绍如何将数据存储到文件中,但如果需要的是简单的存储方案,模块shelve可替
# 你完成大部分工作——你只需提供一个文件名即可。对于模块shelve,你唯一感兴趣的是函数
# open。这个函数将一个文件名作为参数,并返回一个Shelf对象,供你用来存储数据。你可像操
# 作普通字典那样操作它(只是键必须为字符串),操作完毕(并将所做的修改存盘)时,可调用
# 其方法close。
# 1. 一个潜在的陷阱
# 至关重要的一点是认识到shelve.open返回的对象并非普通映射,如下例所示:
# >>> import shelve
# >>> s = shelve.open('test.dat')
# >>> s['x'] = ['a', 'b', 'c']
# >>> s['x'].append('d')
# >>> s['x']
# ['a', 'b', 'c']
# 'd'到哪里去了呢?
# 这很容易解释:当你查看shelf对象中的元素时,将使用存储版重建该对象,而当你将一个
# 元素赋给键时,该元素将被存储。在上述示例中,发生的事情如下。
#  列表['a', 'b', 'c']被存储到s的'x'键下。
#  获取存储的表示,并使用它创建一个新列表,再将'd'附加到这个新列表末尾,但这个修
# 改后的版本未被存储!
#  最后,再次获取原来的版本——其中没有'd'。
# 要正确地修改使用模块shelve存储的对象,必须将获取的副本赋给一个临时变量,并在修改
# 这个副本后再次存储①:
# >>> temp = s['x']
# >>> temp.append('d')
# >>> s['x'] = temp
# >>> s['x']
# ['a', 'b', 'c', 'd']
# 还有另一种避免这个问题的办法:将函数open的参数writeback设置为True。这样,从shelf
# 对象读取或赋给它的所有数据结构都将保存到内存(缓存)中,并等到你关闭shelf对象时才将
# 它们写入磁盘中。如果你处理的数据不多,且不想操心这些问题,将参数writeback设置为True
# 可能是个不错的主意。在这种情况下,你必须确保在处理完毕后将shelf对象关闭。为此,一种
# 办法是像处理打开的文件那样,将shelf对象用作上下文管理器,这将在下一章讨论。
# 2. 一个简单的数据库示例
# 代码清单10-8是一个使用模块shelve的简单数据库应用程序。

# 代码清单10-8 一个简单的数据库应用程序
# # database.py
# import sys, shelve
# def store_person(db):
#  """
#  让用户输入数据并将其存储到shelf对象中
#  """
#  pid = input('Enter unique ID number: ')
#  person = {}
#  person['name'] = input('Enter name: ')
#  person['age'] = input('Enter age: ')
#  person['phone'] = input('Enter phone number: ')
#  db[pid] = person
# def lookup_person(db):
#  """
#  让用户输入ID和所需的字段,并从shelf对象中获取相应的数据
#  """
#  pid = input('Enter ID number: ')
#  field = input('What would you like to know? (name, age, phone) ')
#  field = field.strip().lower()
#  print(field.capitalize() + ':', db[pid][field])
# def print_help():
#  print('The available commands are:')
#  print('store : Stores information about a person')
#  print('lookup : Looks up a person from ID number')
#  print('quit : Save changes and exit')
#  print('? : Prints this message')
# def enter_command():
#  cmd = input('Enter command (? for help): ')
#  cmd = cmd.strip().lower()
#  return cmd
# def main():
#  database = shelve.open('C:\\database.dat') # 你可能想修改这个名称
#  try:
#  while True:
#  cmd = enter_command()
#  if cmd == 'store':
#  store_person(database)
#  elif cmd == 'lookup':
#  lookup_person(database)
#  elif cmd == '?':
#  print_help()
#  elif cmd == 'quit':
#  return
#  finally:
#  database.close()
# if name == '__main__': main()

# 代码清单10-8所示的程序有几个有趣的特征。
#  所有代码都放在函数中,这提高了程序的结构化程度(一个可能的改进是将这些函数作
# 为一个类的方法)。
#  主程序位于函数main中,这个函数仅在__name__== '__main__'时才会被调用。这意味着可
# 在另一个程序中将这个程序作为模块导入,再调用函数main。
#  在函数main中,我打开一个数据库(shelf),再将其作为参数传递给其他需要它的函数。
# 由于这个程序很小,我原本可以使用一个全局变量,但在大多数情况下,最好不要使用
# 全局变量——除非你有理由这样做。
#  读入一些值后,我调用strip和lower来修改它们,因为仅当提供的键与存储的键完全相同
# 时,它们才匹配。如果对用户输入的内容都调用strip和lower,用户输入时就无需太关心
# 大小写,且在输入开头和末尾有多余的空白也没有关系。另外,注意到打印字段名时使
# 用了capitalize。
#  为确保数据库得以妥善的关闭,我使用了try和finally。不知道什么时候就会出现问题,
# 进而引发异常。如果程序终止时未妥善地关闭数据库,数据库文件可能受损,变得毫无
# 用处。通过使用try和finally,可避免这样的情况发生。我原本也可像第11章介绍的那样,
# 将shelf用作上下文管理器。
# 我们来试试这个数据库。下面是一个示例交互过程:

# Enter command (? for help): ?
# The available commands are:
# store : Stores information about a person
# lookup : Looks up a person from ID number
# quit : Save changes and exit
# ? : Prints this message
# Enter command (? for help): store
# Enter unique ID number: 001
# Enter name: Mr. Gumby
# Enter age: 42
# Enter phone number: 555-1234
# Enter command (? for help): lookup
# Enter ID number: 001
# What would you like to know? (name, age, phone) phone
# Phone: 555-1234
# Enter command (? for help): quit

# 这个交互过程并不是很有趣。我原本可以使用普通字典(而不是shelf对象)来完成这个任
# 务。退出这个程序后,来看看再次运行它时(这也许是在第二天)发生的情况。
# Enter command (? for help): lookup
# Enter ID number: 001
# What would you like to know? (name, age, phone) name
# Name: Mr. Gumby
# Enter command (? for help): quit

# 如你所见,这个程序读取前面运行它时创建的文件,该文件依然包含Mr. Gumby!
# 请随便实验这个程序,看看你能否扩展其功能并让它对用户更友好。你或许能够设计出一个
# 可为你所用的版本。

# 10.3.8 re
# 模块re提供了对正则表达式的支持。如果你听说过正则表达式,就可能知道它们有多厉害;
# 如果没有,就等着大吃一惊吧。
# 然而,需要指出的是,要掌握正则表达式有点难。关键是每次学习一点点:只考虑完成特定
# 任务所需的知识。预先将所有的知识牢记在心毫无意义。本节描述模块re和正则表达式的主要功
# 能,让你能够快速上手。

# 1. 正则表达式是什么
# 正则表达式是可匹配文本片段的模式。最简单的正则表达式为普通字符串,与它自己匹配。
# 换而言之,正则表达式'python'与字符串'python'匹配。你可使用这种匹配行为来完成如下工作:
# 在文本中查找模式,将特定的模式替换为计算得到的值,以及将文本分割成片段。
#  通配符
# 正则表达式可与多个字符串匹配,你可使用特殊字符来创建这种正则表达式。例如,句点与
# 除换行符外的其他字符都匹配,因此正则表达式'.ython'与字符串'python'和'jython'都匹配。
# 它还与'qython'、'+ython'和' ython'(第一个字符为空格)等字符串匹配,但不与'cpython'、
# 'ython'等字符串匹配,因为句点只与一个字符匹配,而不与零或两个字符匹配。
# 句点与除换行符外的任何字符都匹配,因此被称为通配符(wildcard)。
#  对特殊字符进行转义
# 普通字符只与自己匹配,但特殊字符的情况完全不同。例如,假设要匹配字符串
# 'python.org',可以直接使用模式'python.org'吗?可以,但它也与'pythonzorg'匹配(还记得吗?
# 句点与除换行符外的其他字符都匹配),这可能不是你想要的结果。要让特殊字符的行为与普通
# 字符一样,可对其进行转义:像第1章对字符串中的引号进行转义时所做的那样,在它前面加上
# 一个反斜杠。因此,在这个示例中,可使用模式'python\\.org',它只与'python.org'匹配。
# 请注意,为表示模块re要求的单个反斜杠,需要在字符串中书写两个反斜杠,让解释器对其
# 进行转义。换而言之,这里包含两层转义:解释器执行的转义和模块re执行的转义。实际上,在
# 有些情况下也可使用单个反斜杠,让解释器自动对其进行转义,但请不要这样依赖解释器。如果
# 你厌烦了两个反斜杆,可使用原始字符串,如r'python\.org'。
#  字符集
# 匹配任何字符很有用,但有时你需要更细致地控制。为此,可以用方括号将一个子串括起,
# 创建一个所谓的字符集。这样的字符集与其包含的字符都匹配,例如'[pj]ython'与'python'和
# 'jython'都匹配,但不与其他字符串匹配。你还可使用范围,例如'[a-z]'与a~z的任何字母都匹
# 配。你还可组合多个访问,方法是依次列出它们,例如'[a-zA-Z0-9]'与大写字母、小写字母和
# 数字都匹配。请注意,字符集只能匹配一个字符。
# 要指定排除字符集,可在开头添加一个^字符,例如'[^abc]'与除a、b和c外的其他任何字符都匹配。

#  二选一和子模式
# 需要以不同的方式处理每个字符时,字符集很好,但如果只想匹配字符串'python'和'perl',
# 该如何办呢?使用字符集或通配符无法指定这样的模式,而必须使用表示二选一的特殊字符:管
# 道字符(|)。所需的模式为'python|perl'。
# 然而,有时候你不想将二选一运算符用于整个模式,而只想将其用于模式的一部分。为此,
# 可将这部分(子模式)放在圆括号内。对于前面的示例,可重写为'p(ython|erl)'。请注意,单
# 个字符也可称为子模式。
#  可选模式和重复模式
# 通过在子模式后面加上问号,可将其指定为可选的,即可包含可不包含。例如,下面这个不
# 太好懂的模式:

# 2. 模块re的内容
# 如果没有用武之地,知道如何书写正则表达式也没多大意义。模块re包含多个使用正则表达
# 式的函数,表10-9描述了其中最重要的一些。

# 表10-9 模块re中一些重要的函数
# 函 数                                       描 述
# compile(pattern[, flags])             根据包含正则表达式的字符串创建模式对象
# search(pattern, string[, flags])      在字符串中查找模式
# match(pattern, string[, flags])       在字符串开头匹配模式
# split(pattern, string[, maxsplit=0])  根据模式来分割字符串
# findall(pattern, string)              返回一个列表,其中包含字符串中所有与模式匹配的子串
# sub(pat, repl, string[, count=0])     将字符串中与模式pat匹配的子串都替换为repl
# escape(string)                        对字符串中所有的正则表达式特殊字符都进行转义

# 函数re.compile将用字符串表示的正则表达式转换为模式对象,以提高匹配效率。调用
# search、match等函数时,如果提供的是用字符串表示的正则表达式,都必须在内部将它们转换
# 为模式对象。通过使用函数compile对正则表达式进行转换后,每次使用它时都无需再进行转换。
# 模式对象也有搜索/匹配方法,因此re.search(pat, string)(其中pat是一个使用字符串表示的正
# 则表达式)等价于pat.search(string)(其中pat是使用compile创建的模式对象)。编译后的正则
# 表达式对象也可用于模块re中的普通函数中。
# 函数re.search在给定字符串中查找第一个与指定正则表达式匹配的子串。如果找到这样的
# 子串,将返回MatchObject(结果为真),否则返回None(结果为假)。鉴于返回值的这种特征,可
# 在条件语句中使用这个函数,如下所示:
# if re.search(pat, string):
#  print('Found it!')
# 然而,如果你需要获悉有关匹配的子串的详细信息,可查看返回的MatchObject。下一节将
# 更详细地介绍MatchObject。
# 函数re.match尝试在给定字符串开头查找与正则表达式匹配的子串,因此re.match('p',
# 'python')返回真(MatchObject),而re.match('p', 'www.python.org')返回假(None)。
# 注意 函数match在模式与字符串开头匹配时就返回True,而不要求模式与整个字符串匹配。如
# 果要求与整个字符串匹配,需要在模式末尾加上一个美元符号。美元符号要求与字符串
# 末尾匹配,从而将匹配检查延伸到整个字符串。
# 函数re.split根据与模式匹配的子串来分割字符串。这类似于字符串方法split,但使用正
# 则表达式来指定分隔符,而不是指定固定的分隔符。例如,使用字符串方法split时,可以字符
# 串', '为分隔符来分割字符串,但使用re. split时,可以空格和逗号为分隔符来分割字符串。
# >>> some_text = 'alpha, beta,,,,gamma delta'
# >>> re.split('[, ]+', some_text)
# ['alpha', 'beta', 'gamma', 'delta']

# 注意 如果模式包含圆括号,将在分割得到的子串之间插入括号中的内容。例如,re.split('o(o)',
# 'foobar')的结果为['f', 'o', 'bar']。

# 从这个示例可知,返回值为子串列表。参数maxsplit指定最多分割多少次。
# >>> re.split('[, ]+', some_text, maxsplit=2)
# ['alpha', 'beta', 'gamma delta']
# >>> re.split('[, ]+', some_text, maxsplit=1)
# ['alpha', 'beta,,,,gamma delta']

# 3. 匹配对象和编组
# 在模块re中,查找与模式匹配的子串的函数都在找到时返回MatchObject对象。这种对象包
# 含与模式匹配的子串的信息,还包含模式的哪部分与子串的哪部分匹配的信息。这些子串部分称
# 为编组(group)。
# 编组就是放在圆括号内的子模式,它们是根据左边的括号数编号的,其中编组0指的是整个
# 模式。因此,在下面的模式中:
# 'There (was a (wee) (cooper)) who (lived in Fyfe)'
# 包含如下编组:
# 0 There was a wee cooper who lived in Fyfe
# 1 was a wee cooper
# 2 wee
# 3 cooper
# 4 lived in Fyfe
# 通常,编组包含诸如通配符和重复运算符等特殊字符,因此你可能想知道与给定编组匹配的
# 内容。例如,在下面的模式中:
# r'www\.(.+)\.com$'
# 编组0包含整个字符串,而编组1包含'www.'和'.com'之间的内容。通过创建类似于这样的模
# 式,可提取字符串中你感兴趣的部分。

# 4. 替换中的组号和函数
# 在第一个re.sub使用示例中,我只是将一个子串替换为另一个。这也可使用字符串方法
# replace(参见3.4节)轻松地完成。当然,正则表达式很有用,因为它们让你能够以更灵活的方
# 式进行搜索,还让你能够执行更复杂的替换。
# 为利用re.sub的强大功能,最简单的方式是在替代字符串中使用组号。在替换字符串中,任
# 何类似于'\\n'的转义序列都将被替换为与模式中编组n匹配的字符串。例如,假设要将
# '*something*'替换为'<em>something</em>',其中前者是在纯文本文档(如电子邮件)中表示突
# 出的普通方式,而后者是相应的HTML代码(用于网页中)。下面先来创建一个正则表达式。
# >>> emphasis_pattern = r'\*([^\*]+)\*'
# 请注意,正则表达式容易变得难以理解,因此为方便其他人(也包括你自己)以后阅读代码,
# 使用有意义的变量名很重要。

# 5. 找出发件人
# 你曾将邮件保存为文本文件吗?如果这样做过,你可能注意到文件开头有大量难以理解的文
# 本,如代码清单10-9所示。
# 代码清单10-9 一组虚构的邮件头
# From foo@bar.baz Thu Dec 20 01:22:50 2008
# Return-Path: <foo@bar.baz>
# Received: from xyzzy42.bar.com (xyzzy.bar.baz [123.456.789.42])
#  by frozz.bozz.floop (8.9.3/8.9.3) with ESMTP id BAA25436
#  for <magnus@bozz.floop>; Thu, 20 Dec 2004 01:22:50 +0100 (MET)
# Received: from [43.253.124.23] by bar.baz
#  (InterMail vM.4.01.03.27 201-229-121-127-20010626) with ESMTP
#  id <20041220002242.ADASD123.bar.baz@[43.253.124.23]>; Thu, 20 Dec 2004 00:22:42 +0000
# User-Agent: Microsoft-Outlook-Express-Macintosh-Edition/5.02.2022
# Date: Wed, 19 Dec 2008 17:22:42 -0700
# Subject: Re: Spam
# From: Foo Fie <foo@bar.baz>
# To: Magnus Lie Hetland <magnus@bozz.floop>
# CC: <Mr.Gumby@bar.baz>
# Message-ID: <B8467D62.84F foo@baz.com> %
# In-Reply-To: <20041219013308.A2655@bozz.floop> Mime- version: 1.0
# Content-type: text/plain; charset="US-ASCII" Content-transfer-encoding: 7bit
# Status: RO
# Content-Length: 55
# Lines: 6
# So long, and thanks for all the spam!
# Yours,
# Foo Fie

# 代码清单10-10 找出发件人的程序
# # find_sender.py
# import fileinput, re
# pat = re.compile('From: (.*) <.*?>$')
# for line in fileinput.input():
#  m = pat.match(line)
#  if m: print(m.group(1))
# 可像下面这样运行这个程序(假设电子邮件保存在文本文件message.eml中):
# $ python find_sender.py message.eml
# Foo Fie
# 对于这个程序,应注意如下几点。
#  为提高处理效率,我编译了正则表达式。
#  我将用于匹配要提取文本的子模式放在圆括号内,使其变成了一个编组。
#  我使用了一个非贪婪模式,使其只匹配最后一对尖括号(以防姓名也包含尖括号)。
#  我使用了美元符号指出要使用这个模式来匹配整行(直到行尾)。
#  我使用了if语句来确保匹配后才提取与特定编组匹配的内容。
# 要列出邮件头中提及的所有邮件地址,需要创建一个只与邮件地址匹配的正则表达式,然后
# 使用方法findall找出所有与之匹配的内容。为避免重复,可将邮件地址存储在本章前面介绍的
# 集合中。最后,提取键,将它们排序并打印出来。

# 6. 模板系统示例
# 模板(template)是一种文件,可在其中插入具体的值来得到最终的文本。例如,可能有一
# 个只需插入收件人姓名的邮件模板。Python提供了一种高级模板机制:字符串格式设置。使用正
# 则表达式可让这个系统更加高级。假设要把所有的'[something]'(字段)都替换为将something
# 作为Python表达式计算得到的结果。因此,下面的字符串:
# 'The sum of 7 and 9 is [7 + 9].'
# 应转换为:
# 'The sum of 7 and 9 is 16.'
# 另外,你还希望能够在字段中进行赋值,使得下面的字符串:
# '[name="Mr. Gumby"]Hello, [name]'
# 转换成:
# 'Hello, Mr. Gumby'
# 这看似很复杂,我们来看看可供使用的工具。
#  可使用正则表达式来匹配字段并提取其内容。
#  可使用eval来计算表达式字符串,并提供包含作用域的字典。可在try/except语句中执行
# 这种操作。如果出现SyntaxError异常,就说明你处理的可能是语句(如赋值语句)而不
# 是表达式,应使用exec来执行它。
#  可使用exec来执行语句字符串(和其他语句),并将模板的作用域存储到字典中。
#  可使用re.sub将被处理的字符串替换为计算得到的结果。突然间,这看起来并不那么吓人
# 了,不是吗?

# 10.3.9 其他有趣的标准模块
# 虽然本章介绍的内容很多,但这只是标准库的冰山一角。为激发你深入探索的兴趣,下面简
# 单说说其他几个很棒的库。
#  argparse:在UNIX中,运行命令行程序时常常需要指定各种选项(开关),Python解释器
# 就是这样的典范。这些选项都包含在sys.argv中,但要正确地处理它们绝非容易。模块
# argparse使得提供功能齐备的命令行界面易如反掌。
#  cmd:这个模块让你能够编写类似于Python交互式解释器的命令行解释器。你可定义命令,
# 让用户能够在提示符下执行它们。或许可使用这个模块为你编写的程序提供用户界面?
#  csv:CSV指的是逗号分隔的值(comma-seperated values),很多应用程序(如很多电子表
# 格程序和数据库程序)都使用这种简单格式来存储表格数据。这种格式主要用于在不同
# 的程序之间交换数据。模块csv让你能够轻松地读写CSV文件,它还以非常透明的方式处
# 理CSV格式的一些棘手部分。
#  datetime:如果模块time不能满足你的时间跟踪需求,模块datetime很可能能够满足。
# datetime支持特殊的日期和时间对象,并让你能够以各种方式创建和合并这些对象。相比
# 于模块time,模块datetime的接口在很多方面都更加直观。
#  difflib:这个库让你能够确定两个序列的相似程度,还让你能够从很多序列中找出与指
# 定序列最为相似的序列。例如,可使用difflib来创建简单的搜索程序。
#  enum:枚举类型是一种只有少数几个可能取值的类型。很多语言都内置了这样的类型,如
# 果你在使用Python时需要这样的类型,模块enum可提供极大的帮助。
#  functools:这个模块提供的功能是,让你能够在调用函数时只提供部分参数(部分求值,
# partial evaluation),以后再填充其他的参数。在Python 3.0中,这个模块包含filter和reduce。
#  hashlib:使用这个模块可计算字符串的小型“签名”(数)。计算两个不同字符串的签名
# 时,几乎可以肯定得到的两个签名是不同的。你可使用它来计算大型文本文件的签名,
# 这个模块在加密和安全领域有很多用途①。
#  itertools:包含大量用于创建和合并迭代器(或其他可迭代对象)的工具,其中包括可
# 以串接可迭代对象、创建返回无限连续整数的迭代器(类似于range,但没有上限)、反复
# 遍历可迭代对象以及具有其他作用的函数。
#  logging:使用print语句来确定程序中发生的情况很有用。要避免跟踪时出现大量调试输
# 出,可将这些信息写入日志文件中。这个模块提供了一系列标准工具,可用于管理一个
# 或多个中央日志,它还支持多种优先级不同的日志消息。
#  statistics:计算一组数的平均值并不那么难,但是要正确地获得中位数,以确定总体标
# 准偏差和样本标准偏差之间的差别,即便对于偶数个元素来说,也需要费点心思。在这
# 种情况下,不要手工计算,而应使用模块statistics!
#  timeit、profile和trace:模块timeit(和配套的命令行脚本)是一个测量代码段执行时
# 间的工具。这个模块暗藏玄机,度量性能时你可能应该使用它而不是模块time。模块
# profile(和配套模块pstats)可用于对代码段的效率进行更全面的分析。模块trace可帮
# 助你进行覆盖率分析(即代码的哪些部分执行了,哪些部分没有执行),这在编写测试代
# 码时很有用。

# 10.4 小结
# 本章介绍了模块:如何创建模块、如何探索模块以及如何使用Python标准库中的一些模块。
#  模块:模块基本上是一个子程序,主要作用是定义函数、类和变量等。模块包含测试代
# 码时,应将这些代码放在一条检查name == '__main__'的if语句中。如果模块位于环境变
# 量PYTHONPATH包含的目录中,就可直接导入它;要导入存储在文件foo.py中的模块,可使
# 用语句import foo。
#  包:包不过是包含其他模块的模块。包是使用包含文件__init__.py的目录实现的。
#  探索模块:在交互式解释器中导入模块后,就可以众多不同的方式对其进行探索,其中
# 包括使用dir、查看变量__all__以及使用函数help。文档和源代码也是获取信息和洞见的
# 极佳来源。
#  标准库:Python自带多个模块,统称为标准库。本章介绍了其中的几个。
#  sys:这个模块让你能够访问多个与Python解释器关系紧密的变量和函数。
#  os:这个模块让你能够访问多个与操作系统关系紧密的变量和函数。
#  fileinput:这个模块让你能够轻松地迭代多个文件或流的内容行。
#  sets、heapq和deque:这三个模块提供了三种很有用的数据结构。内置类型set也实现
# 了集合。
#  time:这个模块让你能够获取当前时间、操作时间和日期以及设置它们的格式。
#  random:这个模块包含用于生成随机数,从序列中随机地选择元素,以及打乱列表中元
# 素的函数。
#  shelve:这个模块用于创建永久性映射,其内容存储在使用给定文件名的数据库中。
#  re:支持正则表达式的模块。
# 如果你想更深入地学习模块,再次建议浏览“Python库参考手册”,它读起来真的很有趣。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_44119674

觉得有帮助,鼓励下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值