Python学习笔记【十】——《python基础教程》:: 充电时刻

来源:互联网 发布:苹果手机数据免费恢复 编辑:程序博客网 时间:2024/05/18 23:16

10. 充电时刻

  Python的标准安装包括一组模块,称为标准库(standard library)。

10.1. 模块

10.1.1. 模块是程序

  任何Python程序都可以作为模块导入。假定编写hello.py文件如下:

#hello.pyprint "hello, world!"

  假设将它保存在/mnt/ext-holly/python中,执行以下代码,将模块绝对路径添加到sys.path中,通知解释器增加模块的查找位置:

>>> import sys>>> sys.path.append("/mnt/ext-holly/python")

  之后,可以在python中导入自己的模块:

>>> import hellohello, world!

  NOTE:导入模块是,会产生新文件——hello.pyc。以.pyc为扩展名的文件是经过处理(编译)的,已转换成Python能更加有效处理的文件,以供Python导入。


  再次导入hello模块,会产生下述现象:

>>> import hello>>>               #再次导入hello模块,不再输出

  导入模块主要用于定义(变量、函数、类等)。事实上,定义操作只需要执行一次,因此导入多次和导入一次的效果是一样的


  为什么只是一次?
  这种“只导入一次”(import-only-once)的行为大多数情况下是一种实质性优化,对于以下情况尤其重要:两个模块互相导入
  假设两个模块可以互相导入,当每个模块都可以导入数次时,就出问题了。可以想象:这种情况下导入就成了无限循环。但是,因为在第二次导入模块的时候什么都不会发生,所以循环会终止。
  若坚持重新载入模块,那么可以使用内建的reload函数。它带将要重新载入的模块作为参数,并返回重新载入的模块。若你的程序运行的时候更改了模块并希望将这些更改反映出来,那么这个功能会比较有用。要重新载入hello模块,可以像下面这样做:

>>> hello = reload(hello) #使用重新载入的版本替换了原先的版本hello, world!

  假设已经使用旧模块中的类创建了对象x。该类发生了变化,并使用reload对其重新导入,那么x依然是修改前的类的对象。若想希望x是修改后类的对象,需要对其重新创建。
  ★Python3.0已经去掉了reload函数,虽然exec可以实现同样功能,但应尽量避免重新载入模块。
  

10.1.2. 模块用于定义

  模块真正的用处在于它们可以保持自己的作用域。意味着定义的所有类和函数以及赋值后的变量都成为了模块的特性。

1. 在模块中定义函数

  假设编写代码段,放在hello2.py文件(假设将其放置在Python解释器能找到的地方)。

#hello2.pydef hello():  print "hello, world!"

  可以通过下面方式导入执行:

>>> import hello2>>> hello2.hello()hello, world!

  ★为了让代码可重用,请将它模块化!!!

2. 在模块中添加测试代码

  模块用来定义函数、类和其他一些内容,有时在模块中添加一些检查模块本身是否正常工作的测试代码是很有用的。例如模块hello3:

#hello3.pydef hello():  print "hello, world!"#testhello()

  上述代码导入和调用时,hello()函数都会被调用。

>>> import hello3hello, world!>>> hello3.hello()hello, world!

  显然这不是我们想要的。避免上述情况的关键在于:“告知”模块本身是作为程序运行还是导入到其他程序。
  此时需要使用__name__变量。__name__在“主程序”(包括解释器交互提示符界面)中的值为‘__main__’;在导入的模块中,被设定为模块的名字。
  因此,可以在测试代码前添加判断,例如模块hello4:

#hello4.pydef hello():  print "hello, world!"def test():  hello()#testif __name__ == '__main__': test()

  此时导入时不会运行测试代码。

>>> import hello4>>> hello4.hello()hello, world!

10.1.3. 让你的模块可用

  理想情况下,sys.path本身就应该包含正确的目录。有两种方法可以做到这一点:
  ■ 将模块放在合适的位置
  ■ 告诉解释器去哪里查找需要的模块

1. 将模块放置在正确位置

  只需要知道Python解释器从哪里查找模块,将文件放在那里即可。通过查看sys.path变量:

>>> import sys, pprint>>> pprint.pprint(sys.path)['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/gtk-2.0']

  只要将模块放入上述目录中,所有程序就都能将其导入了。
  若Python解释器是由管理员安装的,而使用者没有管理权限,可能无法将模块存储在Python使用的目录中。此时需要另一解决方案:告诉解释器去哪里找。  

2. 告诉解释器去哪里找

  这个解决方案对以下几种情况并不适用:
  ♦ 不希望将自己的模块填满Python解释器的目录;
  ♦ 没有在Python解释器目录中存储文件的权限;
  ♦ 想将模块放在其他地方。
  最后一种情况就需要告诉解释器去哪里找。之前介绍一种方法:修改sys.path变量。而标准的实现方法是:在PYTHONPATH环境变量中包含模块所在的目录。


  环境变量
  环境变量是操作系统的一部分。它相当于Python变量,但在Python解释器外设置。设置方法需要参考操作系统文档,例如:
  1. UNIX / Mac OSX:在每次登录都要执行的shell文件内设置环境变量。假设使用类似bash的shell文件,要设置的是.bashrc文件;
  2. Windows:使用控制面板编辑变量。


  NOTE:不需要使用PYTHONPATH来更改sys.path。可以让Python替你完成这些工作。路径配置文件是以.pth为扩展名的文件,包括应添加到sys.path中的目录信息,空行和以#开头的行会被忽略,以import开头的文件会被执行。为了执行路径配置文件,需要将其放置在可以找到的地方。

3. 命名模块

  包含模块代码的文件的名字要和模块名一样——再加上.py扩展名(Windows还可以使用.pyw扩展名)。

10.1.4. 包

  包(package):是另一类模块,它们能包含其他模块。
  当模块存储在文件(.py)中时,包就是模块所在的目录。为了使Python将其识别为包,需要包含一个名为__init__.py的文件。若将它作为普通模块导入的话,文件的内容就是包的内容。
  例如,有个名为constants的包,文件constants/__init__.py包含语句PI=3.14,那么:

import constantsprint constants.PI

  假设建立一个叫做drawing德保,其中包括名为shapes和colors的模块,需要创建的目录和文件如下表所示:

文件目录 描述 ~/python/ PYTHONPATH中的目录 ~/python/drawing/ 包目录(drawing包) ~/python/drawing/__init__.py 包代码(drawing模块) ~/python/drawing/colors.py colors模块 ~/python/drawing/shapes.py shapes模块

  依照表中设置,下述语句都是合法的:

import drawing              #(1)imports the drawing packageimport drawing.colors       #(2)imports the colors modulefrom drawing import shapes  #(3)imports the shapes module

10.2. 探究模块

10.2.1. 模块中有什么

  探究模块最直接的方式是在Python解释器中研究他们。例如,假设有个copy的标准模块:

import copy

  未发生异常,说明它是存在的。

1. 使用dir

  使用dir函数,可以将对象(以及模块的所有函数、类、变量等)的所有特性列出。例如,查看copy模块:

>>> dir(copy)['Error', 'PyStringMap', '_EmptyClass', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_copy_dispatch', '_copy_immutable', '_copy_inst', '_copy_with_constructor', '_copy_with_copy_method', '_deepcopy_atomic', '_deepcopy_dict', '_deepcopy_dispatch', '_deepcopy_inst', '_deepcopy_list', '_deepcopy_method', '_deepcopy_tuple', '_keep_alive', '_reconstruct', '_test', 'copy', 'deepcopy', 'dispatch_table', 'error', 'name', 't', 'weakref']

2. __all__变量

  __all__变量定义了模块的公有接口。

>>> copy.__all__['Error', 'copy', 'deepcopy']

  当使用from copy import *导入模块copy后,只能使用__all__变量中的函数。
  若要使用__all__变量之外的函数,需要显式地实现,或者导入copy后使用copy.function,或者使用from copy import function

  ★ 在写模块时,设置__all__是相当有用的。因为模块中可能有很多其他程序不需要的变量、函数和类,__all__会将他们过滤出去。若没有__all__,用import *将输出模块中所有不以下划线开头的全局名称。  

10.2.2. 用help获取帮助

  help函数能够提供日常所需的信息。例如:

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

  上面提到__doc__特性,help函数获得的帮助信息就是从__doc__特性中提取出来的。

>>> print copy.copy.__doc__Shallow copy operation on arbitrary Python objects.    See the module's __doc__ string for more info.

  可以看出help函数会打印更多的信息,例如函数签名(函数所带的参数)。

10.2.3. 文档

  模块信息的自然来源就是文档
  以range为例,不用在Python书籍或者标准Python文档中寻找有关range的描述,可以直接查看:

>>> print range.__doc__range(stop) -> list of integersrange(start, stop[, step]) -> list of integersReturn a list containing an arithmetic progression of integers.range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.When step is given, it specifies the increment (or decrement).For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!These are exactly the valid indices for a list of 4 elements.

  并非每个模块和函数都有好的文档字符串,有些时候可能需要十分透彻地描述这些模块和函数是如何工作的,可以参考以下文档:
  Python库参考对所有标准库中的模块都有描述;
  Python指南
  Python语言参考

10.2.4. 使用源代码

  阅读源代码,是学习Python最好的方式。
  但是,真正的问题在于源代码的位置。可以使用__file__属性:

>>> print copy.__file__/usr/lib/python2.7/copy.pyc

  找到copy.py文件后,可以使用文本编辑器打开。此时需要承担意外修改的风险,关闭文件时需要注意。
  
  ★一些模块并不包含可以阅读的Python源代码。他们可能融入到解释器内(sys),或者是使用C语言写成的。

10.3. 标准库:一些最爱

  本章标题“充电时刻”(batteries included)短语最开始是由Frank Stajano创造,用于描述Python丰富的标准库。本章会对部分标准模块进行说明,强调每个模块比较有趣的特征。

10.3.1. sys

  sys这个模块让你能访问与Python解释器联系紧密的变量和函数。其中一些在下表中列出。

函数/变量 描述 备注 argv 命令行参数,包括脚本名称 exit([arg]) 退出当前的程序,可选参数为给定的返回值或者错误信息 arg参数用来表示程序是否成功退出。
一般使用整数(0表示成功),或使用字符串,显示错误信息 modules 映射模块名字到载入模块的字典 将模块名映射到实际存在的模块上,只应用于目前导入的模块 path 查找模块所在目录的目录名列表 解释器查找模块的目录名(字符串)列表 platform 类似sunos5或者win32的平台标识符 解释器所在的“平台”名称。 stdin 标准输入流——一个类文件(file-like)对象 stdout 标准输出流——一个类文件对象 stderr 标准错误流——一个类文件对象

  
  参照反转参数(reverseargs.py)的例子。

#reverseargs.pyimport sysargs = sys.argv[1:] #跳过argv的第一个元素——脚本名字args.reverse()      #使用args保存待修改的参数列表,不修改原始列表保持安全性print ' '.join(args)

  测试结果如下:

# python reverseargs.py this is a testtest a is this

10.3.2. os

  os模块提供了访问多个操作系统服务的功能。其中一些常用函数和变量在下表中列出:

函数/变量 描述 备注 environ 对环境变量进行映射 例如:访问系统变量PYTHONPATH,可以使用os.environ[‘PYTHONPATH’]
这个映射可用来修改系统环境变量,但并非所有系统都支持。 system(command) 对子shell中执行操作系统命令 还有其他函数可执行外部程序。
例如execv,它会退出Python解释器,并将控制权交给被执行程序。 sep 路径中的分隔符 UNIX: “/”
Windows: “\”
MacOS: “:” pathsep 分隔路径的分隔符 UNIX: “:”
Windows: “;”
MacOS: “::” linesep 行分隔符 UNIX:换行符(\n)
Windows:回车(\r)
MacOS:两者组合(\r\n) urandom(n) 返回n字节的加密强随机数据 若使用的平台不支持,会收到NotImplementError异常。

  
  例如,使用system启动浏览器。UNIX中,

>>> os.system("/usr/bin/firefox")

  Windows中,执行以下代码:

>>> os.system(r'c:\"Program File"\"Mozilla Firefox"\firefox.exe')

  Windows中,带有空格的目录需要单独放在“”中,否则DOS会在空格处停下。另外,若作为程序运行,会打开浏览器;若在IDE中运行该代码,DOS窗口会出现,关闭DOS窗口后,浏览器才会出现。因此,使用os.system在Windows系统中启动浏览器并不是完美的解决方案。可以选用os.startfile

>>> os.startfile(r'c:\Program File\Mozilla Firefox\firefox.exe')

  可以看到,os.startfile中,带有空格的目录不需要特殊处理。

  NOTE:在不同系统中使用os.system启动外部程序后,原Python程序会有不同的表现:Windows中原Python程序会继续运行,UNIX的原Python程序会终止直到os.system启动的程序结束。


更好的解决方案:WEBBROWSE
  启动浏览器可以使用webbrowser模块

>>>import webbrowser>>>webbrowser.open('http://www.python.org')

10.3.3. fileinput

  fileinput模块可以轻松便利文本文件的所有行,可以对提供给标准输入的文本进行遍历。其中重要的函数如下表所示:

函数 描述 备注 input([files[, inplace[, backup]]]) 便于遍历多个输入流的行 filename() 返回当前文件的名称 包括当前正在处理的文本行的文件 lineno() 返回当前(累计)的行数 完成一个文件处理后,行数不会重置,处理下个文件时,将上个文件最后行数加1作为计数的起始 filelineno() 返回当前文件的行数 处理完一个文件后,行数清零,处理下一个文件时,行数重置为1 isfirstline() 检查当前行是否是文件的第一行 isstdin() 检查最后一行是否来自sys.stdin nextfile() 关闭当前文件,移动到下一个文件 跳过的行不计 close() 关闭序列

  
  下面举例演示fileinput的使用。实现对文件代码行进行编码,行号以注释的形式显示在每行的右侧。

#numberlines.py                          import fileinputfor line in fileinput.input(inplace=True):  line = line.rstrip()  num = fileinput.lineno()  print '%-40s # %2i' % (line, num)

  程序按以下方式运行:

python numberlines.py numberlines.py

  修改后的numberlines.py文件内容如下:

#numberlines.py                          #  1                                         #  2import fileinput                         #  3                                         #  4for line in fileinput.input(inplace=True): #  5  line = line.rstrip()                   #  6  num = fileinput.lineno()               #  7  print '%-40s # %2i' % (line, num)      #  8

  NOTE:使用inplace参数很容易破坏文件。需要在不使用inplace的情况下详细测试程序,确保程序正常工作后再修改文件。

10.3.4. 集合、堆和双端队列

1. 集合

  集合(Set)在Python2.3引入。Set类位于sets模块中。集合通过set类型的实现成为语言的一部分,不需要导入sets模块。

>>> set(range(10))set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

  集合由序列(其他可迭代对象)构建。主要用于检查成员资格,因此副本被忽略

>>> set([0,1,2,0,1,2,3,4,5])set([0, 1, 2, 3, 4, 5])

  集合元素的顺序是随意的,不能以元素的顺序作为依据进行编程:

>>> set(["aaa", "vvv", "ccc"])set(['vvv', 'aaa', 'ccc'])

  集合还可以用于标准的集合操作。下面列举一些数学方法及其对应的运算符:

>>> a = set([1, 2, 3])>>> b = set([2, 3, 4])#求并集>>> a.union(b)set([1, 2, 3, 4])>>> a | bset([1, 2, 3, 4])>>> c = a & b#是否是子集>>> c.issubset(a)True>>> c <= aTrue#是否是超集>>> c.issuperset(a)False>>> c >= aFalse#求交集>>> a.intersection(b)set([2, 3])>>> a & bset([2, 3])#求差集>>> a.difference(b)set([1])>>> a - bset([1])#求对称差集>>> a.symmetric_difference(b)set([1, 4])>>> a ^ bset([1, 4])>>> a.copy()set([1, 2, 3])>>> a.copy() is aFalse

NOTE:若需要函数用于查找并打印两个集合的并集,可以使用set类型的union方法的未绑定版本。

>>> mySets = []>>> for i in range(10):...   mySets.append(set(range(i, i+5)))... >>> mySets[set([0, 1, 2, 3, 4]), set([1, 2, 3, 4, 5]), set([2, 3, 4, 5, 6]), set([3, 4, 5, 6, 7]), set([8, 4, 5, 6, 7]), set([8, 9, 5, 6, 7]), set([8, 9, 10, 6, 7]), set([8, 9, 10, 11, 7]), set([8, 9, 10, 11, 12]), set([9, 10, 11, 12, 13])]>>> reduce(set.union, mySets)set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])

  集合是可变的,不能用作字典的键。集合本身只能包含不可变值,若要包含其他集合,需要使用frozenset类型(表示不可变集合)。

>>> a = set()>>> b = set()>>> a.add(b)Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: unhashable type: 'set'>>> a.add(frozenset(b))

2. 堆

  堆(heap)是优先队列的一种。
  使用优先队列能以任意顺序增加对象,并在任何时间找到(移除)最小元素,它比用于列表的min方法有效率的多。
  事实上,Python中没有独立的堆类型,只有包含堆操作函数的模块——heapq。要求必须将列表作为堆对象。
  函数列表如下表:

函数 描述 备注 heappush(heap, x) 将x入堆 heappop(heap) 将队中最小的元素弹出 弹出的元素一般是索引0处的元素,并确保剩余元素中最小的那个占据这个位置 heapify(heap) 将heap属性强制应用到任意一个列表 heapreplace(heap, x) 将对中最小的元素弹出,同时将x入堆 这样做比调用heappop后再调用heappush更高效 nlargest(n, iter) 返回iter中第n大的元素 nsmallest(n, iter) 返回iter中的n小的元素
heappush

  它只能用于通过各种堆函数建立的列表中。因为,堆列表元素顺序并不是随意排列,而是遵循一定规则:

>>> from heapq import *>>> from random import shuffle>>> data = range(10)>>> heap = []>>> for n in data:...   heappush(heap, n)... >>> heap[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> heappush(heap, 0.5)>>> heap[0, 0.5, 2, 3, 1, 5, 6, 7, 8, 9, 4]

  堆列表的元素排列遵循堆属性(heap property),它是底层对算法的基础。具体规则为:位于i位置上的元素总比i//2位置处的元素大。

heappop

  它隐藏了一个比较巧妙的功能:在弹出最小元素后,会对堆列表进行一些操作确保堆列表依然遵循堆属性。如下述代码:

>>> heappop(heap)0>>> heap[0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> heappop(heap)0.5>>> heap[1, 3, 2, 7, 4, 5, 6, 9, 8]>>> heappop(heap)1>>> heap[2, 3, 5, 7, 4, 8, 6, 9]
heapify
>>> heap = [5, 8, 0, 03, 6, 7, 9, 1, 4, 2]>>> heapify(heap)>>> heap[0, 1, 5, 3, 2, 7, 9, 8, 4, 6]
heapreplace

  延续heapify中的代码来演示heapreplace的功能,如下所示:

>>> 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]
nlargest & nsmallest

  nlargest和nsmallest的功能还可以通过排序和分片来完成,但是使用堆算法有以下优点:
  · 效率更高
  · 更有效地利用内存
  · 更易用。  

3. 双端队列(以及其他集合类型)

  双端队列(Double-ended queue, 或deque)在需要按照元素增加的顺序来移除元素时非常有用。


collections模块
  Python2.4增加collections模块,包含dequeue类型。
  Python2.5的collections模块只包括deque类型和defaultdict类型。
  未来可能会加入二叉树(B-Tree)和斐波那契堆(Fibonacci heap)。


  双端队列通过可迭代对象创建,且拥有非常有用的方法。例如:

>>> from collections import deque>>> q = deque(range(5))>>> q.append(5)     #在队列右侧添加元素>>> q.appendleft(6) #在队列左侧添加元素>>> qdeque([6, 0, 1, 2, 3, 4, 5])>>> q.pop()5>>> q.popleft()6>>> q.rotate(3)     #右移3个元素>>> qdeque([2, 3, 4, 0, 1])>>> q.rotate(-1)    #右移-1个元素,即左移1个元素>>> qdeque([3, 4, 0, 1, 2])

  双端队列另外两个方法:
  extend:将可迭代的对象添加到队列右侧;
  extendleft:将可迭代的对象 反序 添加到队列左侧。

10.3.5. time

  time模块包含的函数能实现以下功能:
  · 获得当前时间;
  · 操作时间和日期;
  · 从字符串读取时间;
  · 格式化时间为字符串。
  
  日期有两种表示方式:
  1. 实数:从“新纪元”的1月1日0点起计算到现在的描述。“新纪元”是一个与平台相关的年份。
  2. 包含9个整数的元组:元组内整数含义如下表所示:

索引 字段 值 备注 0 年 1 月 范围1~12 2 日 范围1~31 3 时 范围0~23 4 分 范围0~59 5 秒 范围0~61 0~61是为了应付闰秒和双闰秒 6 周 当周一为0时,范围0~6 7 儒历日 范围1~366 8 夏令时 0、1、-1 数据类型为布尔型,但若使用-1,mktime会工作正常。

  
  例如有元组:(2008, 1, 21, 12, 2, 56, 0, 21, 0)。表示2008年1月21日12点2分56秒,星期一,是当年的第21天(无夏令时)。
  
  time模块中最重要的函数如下表所示,其他未介绍到的函数可以参考Python库参考的15.3节。

函数 描述 备注 asctime([tuple]) 将当前时间元组转换为字符串 >>> time.asctime()
’Thu Dec 7 15:28:15 2017’ localtime([secs]) 将秒数转换为时间元组,以本地时间为基准 若想获得全球统一时间,可以使用gmtime mktime(tuple) 将时间元组转换为描述 sleep(secs) 休眠(不做任何事情)secs秒 strptime(string[, format]) 将字符串解析为时间元组 time() 当前时间(新纪元开始后的描述,以UTC为准)

  
  Python中有两个与时间密切相关的模块:datetime(支持日期和时间的算法)和timeit(帮助开发人员对代码段的执行时间进行计时)。
  

10.3.6. random

  random模块包括返回随机数的函数,可用于模拟或产生随机输出的程序。


  NOTE:
  这里产生的数字都是伪随机数(pseudo-random),看似随机,事实上是以一个可预测的系统作为基础。该可预测系统在伪装随机数方面表现十分优秀,除非需要实现强加密的目标,不需要对其过多担心。
  若需要真的随机数,可以使用os模块的urandom函数
  random模块内的SystemRandom类也是基于同种功能,可以让数据接近真正的随机数。


  模块中一些重要函数如下表所示:

函数 描述 备注 random() 返回0≤n<1之间的随机实数n,其中0<n≤1 getrandbits(n) 以长整型形式返回n个随机位 当处理真正的随机事务(如加密),这个函数尤为有用。 uniform(a, b) 返回随机实数n,其中a≤n<b 平均分布的选择
还类似函数,可根据不同规则(贝塔分布、指数分布、高斯分布等)提取随机数的函数。 randrange([start, ]stop[, step]) 返回range(start, stop, step)中的随机数 choice(seq) 从序列seq中返回随意元素 均一地选择 shuffle(seq[, random]) 原地指定序列seq 给定序列的元素进行随机移位,每种排列的可能性近似相等 sample(seq, n) 从序列seq中选择n个随机且独立的元素 确保元素互不相等

举例说明random模块。

uniform

  下面是随机选取时间点的例子:

>>> from random import *>>> from time import *>>> date1 = (2008, 1, 1, 0, 0, 0, -1, -1, -1) #指定起始时间点,用-1表示周中某天,年中某天和夏令时,Python会自行计算这三个值>>> time1 = mktime(date1)>>> date2 = (2009, 1, 1, 0, 0, 0, -1, -1, -1) #指定终止时间点>>> time2 = mktime(date2)>>> random_time = uniform(time1, time2)       #均一地选取随机时间点>>> print asctime(localtime(random_time))     #将时间转换为易读形式Wed May 28 23:25:31 2008
randrange  

  下面例子根据投掷的骰子数和每个骰子面数,并随机生成点数。
  python源码如下:

#randrange.pyfrom random import randrangenum = input('How many dice? ')  #指定骰子个数sides = input('How many sides per die? ')  #指定骰子面数sum = 0for i in range(num): sum += randrange(sides) + 1 #随机获取每个骰子的点数并相加print 'This result is', sum

  运行结果如下:

# python randrange.py How many dice? 3How many sides per die? 6This result is 14
choice

  下面例子实现随机选择输入文件的行。源码如下:

#fortune.pyimport fileinput, randomfortunes = list(fileinput.input()) #获取输入文件的所有行,保存为列表fortunesprint random.choice(fortunes)      #随机选取某行

  运行结果如下:

# python fortune.py /usr/share/dict/wordsmisalliance's
shuffle

  下面例子实现在每次敲击回车时,为自己发一张牌,同时确保不会获得相同的牌。分为两步进行:
  STEP1:创建一副牌:

>>> values = range(1, 11) + 'Jack Queen King'.split() #获取牌的大小>>> suits = 'diamonds clubs hearts spades'.split()    #获取牌的花色>>> deck = ['%s of %s' % (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])['1 of diamonds', '9 of spades', '8 of diamonds', '8 of spades', '8 of hearts', 'Jack of diamonds', 'King of spades', 'Queen of hearts', '10 of spades', '9 of hearts', '3 of clubs', '5 of hearts']>>> while deck: raw_input(deck.pop()) #下面使用while循环演示发牌的过程... 7 of diamonds #每次回车后会打印空行是因为raw_input反悔了输入的内容并打印。''            #为了能够忽略它,可以将raw_input的值赋值给一些不再用到的变量1 of hearts''7 of spades''…… #省略中间重复步骤''8 of hearts''8 of spades''8 of diamonds''9 of spades''1 of diamonds''

10.3.7. shelve

  shelve模块实现一个简单的存储方案。使用步骤如下:
  · 调用shelve模块的函数open,返回一个shelf对象,可以用它来存储内容;
  · 将shelf对象作为普通的字典(键一定是字符串)来操作;
  · 存储结束后,调用close方法。

1. 潜在的陷阱

  shelve.open函数返回的对象不是普通的映射。举例说明:

>>> import shelve>>> s = shelve.open('test.dat')>>> s['x'] = ['a', 'b', 'c'] #列表['a', 'b', 'c']存储在键‘x’下>>> s['x'].append('d') #获取存储的表示,根据它来创建新的列表,而‘d’被添加到这个副本中。修改后的版本没有被表示。>>> s['x'] #再次获得原始版本——没有‘d’['a', 'b', 'c'] 

  为了正确使用shelve模块修改存储的对象,必须将临时变量绑定到获取的副本上,并重新存储修改后的副本。

>>> temp = s['x']>>> temp.append('d')>>> s['x'] = temp>>> s['x']['a', 'b', 'c', 'd']

  PS:针对上述问题,Python2.4后有另一个解决方法:将open函数的writeback参数设置为true。如此设置后,所有从shelf读取或赋值到shelf的数据结构都会保存在内存中,只有在关闭shelf的时候才会写回到磁盘。

2. 简单的数据库示例

  下面使用shelve模块实现一个简单的数据库应用程序。

#database.py —— 将所有功能放到函数中,模块化import sys, shelvedef store_person(db):  """  Query user for data and store it in the shelf object  """  pid = raw_input('Enter unique ID number: ')  person = {}  person['name'] = raw_input('Enter name: ')  person['age'] = raw_input('Enter age: ')  person['phone'] = raw_input('Enter phone number: ')  db[pid] = persondef lookup_person(db):  """  Query user for ID and desired field, and fetch the corresponding dat from the shelf object  """  pid = raw_input('Enter ID number: ')  field = raw_input('What would you like to know? (name, age, phone)')  field = field.strip().lower()  print field.capitalize() + ':', db[pid][field] # field首字母大写def print_help():  print 'The available commands are:'  print 'store  : Stores information about a person'  print 'loopup : Looks up a person from ID number'  print 'quit   : Save changes and exit'  print '?      : Prints this message'def enter_command():  cmd = raw_input('Enter command(? for help): ')  cmd = cmd.strip().lower() # 允许用户随意使用大小写字母和空格  return cmddef main():  database = shelve.open('database.dat') # You may want to change this name  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: # try/finally确保数据库能够正确关闭,否则数据库文件可能被损坏    database.close()if __name__ == '__main__':main() # main只有在__name__=='__main__'时作为主程序被调用;                                 # 可以在其他模块中将这个程序作为模块导入,然后调用main函数

  测试一下数据库交互过程:

# python database.py Enter command(? for help): ?The available commands are:store  : Stores information about a personloopup : Looks up a person from ID numberquit   : Save changes and exit?      : Prints this messageEnter command(? for help): storeEnter unique ID number: 666Enter name: hollyEnter age: 66Enter phone number: 010-66666666Enter command(? for help): lookupEnter ID number: 666What would you like to know? (name, age, phone)nameName: hollyEnter command(? for help): quit

  数据库文件建立并正确退出后,将会保存上次存储的条目。再执行上述命令可以直接查询数据库内容,例如:

# python database.py Enter command(? for help): lookupEnter ID number: 666What would you like to know? (name, age, phone)phonePhone: 010-66666666

10.3.8. re

  re模块包含对正则表达式(regular expression)的支持。
  NOTE:除标准文档外,Andrew Kuchling的“Regular Expression HOWTO”(正则表达式HOWTO)也是学习Python中使用正则表达式的游泳资源。

1. 什么是正则表达式

  正则表达式是可以匹配文本片段的模式。
  最简单的正则表达式就是普通字符串,可以匹配其自身。例如,正则表达式’python’可以匹配字符串’python’。
  可以用这种匹配行为搜索文本中的模式,并用计算后的值替换特定模式,或将文本进行分段。

· 通配符

  Python中,点号(’.’)可以匹配除换行符外的任意单个字符,可以成为通配符(wildcard)
  例如,正则表达式’.ython’可以匹配字符串’python’、’jython’、’qython’、’+ython’或’ ython’,但不会匹配’cpython’或’ython’,因为点号只能匹配一个字母。

· 对特殊字符进行转义

  为了让正则表达式中的特殊字符(例如点号)表现得像普通字符一样,需要对他进行转义(escape)
  转义方法:在特殊字符前添加反斜线。例如,使用’python.org’可能会匹配’python.org’、’pythonaorg’或者’pythonzorg’;而使用’python\\.org’只会匹配’python.org’。
  NOTE:上述添加转义的正则表达式中需要两个级别的转义:(1) 通过解释器转义;(2) 通过re模块转移。若不想使用双斜线,可以使用原始字符串,例如r’python.org’。

· 字符集

  字符集(character set)可以匹配它所包含的任意(一个)字符。
  创建字符集方法:使用中括号括住字符串。例如,’[a-z]’能够匹配a到z的任意一个字符;’[a-zA-Z0-9]’能够匹配任意一个大小写字母和数字。
  反转字符集方法:在中括号内的字符串开头使用’^’字符。例如,’[^abc]’可以匹配任何除了a、b和c之外的字符。
  NOTE:字符集中,通常没有必要对特殊字符(点号、星号、问号等)进行转义。但需要明确以下几点:
  ♦ 若脱字符(^)出现在字符集的开头,那么需要对其进行转义,除非希望它用作否定运算符;
  ♦ 右中括号(])和横线(-)应该放在字符集的开头(横线也能放在末尾)或者用反斜线进行转义。

· 选择符和子模式

  选择符:管道符号(|),用于连接需要匹配的字符串。例如,只想匹配字符串’python’和’perl’,可以使用模式’python|perl’。
  子模式(subpattern):使用圆括号括起模式的一部分。例如,只想匹配字符串’python’和’perl’,可以使用模式’p(ython|erl)’。

· 可选项和重复子模式

  可选项:在子模式后面加问号,表示它可能出现在匹配字符串中。例如:模式r’(http://)?(www\.)?python\.org)’只能匹配下列字符串:
  ‘http://www.python.org’
  ‘http://python.org’
  ‘www.python.org’
  ‘python.org’
  ★上述匹配模式需要注意以下几个问题:
  1. 对点号进行转义,防止被识别为通配符;
  2. 使用原始字符串,减少反斜线数量;
  3. 每个子模式都用圆括号括起;
  4. 可选子模式出现与否均可,且相互独立。
  重复子模式:允许匹配的子模式重复出现多次,有以下几种形式:
  1. (pattern)*:允许模式重复0次或多次;
  2. (pattern)+:允许模式重复1次或多次;
  3. (pattern){m,n}:允许模式重复m~n次。
  例如:
  ♦ r’w*\.python\.org’可以匹配’www.python.org’、’.python.org’、’ww.python.org’和’wwwwwww.python.org’;
  ♦ r’w+\.python\.org’可以匹配’w.python.org’,但不能匹配’.python.org’;
  ♦ r’w{3,4}\.python\.org’只能匹配’www.python.org’和’wwww.python.org’。

· 字符串的开始和结尾

  标记开始:匹配字符串开头使用脱字符(^)。
  标记结尾:匹配字符串结尾使用美元符号($)。
  
NOTE:关于正则表达式运算符的完整列表,参见Python类参考的7.2.1. Regular Expression Syntax。

2. re模块的内容

  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) 将字符串中所有特殊正则表达式字符转义

  
  NOTE:表中很多参数包含了flags参数,该参数用于改变解释正则表达式的方法。具体参照Python库参考的7.2.2. Module Contents。

模式对象

  使用re.compile函数将字符串表示的正则表达式转换为模式对象,可以更有效率地进行匹配。
  在调用search或match函数时,若pattern为字符串表示的正则表达式,它会在内部将其转换为正则表达式对象;pattern可以直接使用模式对象。
  模式对象本身也有查找和匹配函数。所以,re.search(pat, string)(pat是字符串表示的正则表达式)等价于pat.search(string)(pat是模式对象)。

  使用re.search函数找到子字符串后,会返回MatchObject(值为True),否则返回None(值为False)。
  若希望获得更多关于匹配子字符串的信息,可以检查返回的MatchObject对象。

match

  默认情况下,match函数只匹配字符串的开始部分。例如,match(‘p’, ‘python’)返回MatchObject;而re.match(‘p’, ‘www.python.org’)返回None。
  若要求模式匹配整个字符串,可在模式的结尾加上美元符号。

split

  下例演示使用任意长度的逗号和空格来分割字符串:

>>> some_text = 'alpha, beta,,,gamma delta'>>> re.split('[, ]+', some_text)  #返回子字符串的列表['alpha', 'beta', 'gamma', 'delta']

  split函数的maxsplit参数表示字符串可以分割的最多部分,例如:

>>> re.split('[, ]+', some_text, maxsplit=2)['alpha', 'beta', 'gamma delta']>>> re.split('[, ]+', some_text, maxsplit=1)['alpha', 'beta,,,gamma delta']

  NOTE: split的匹配模式中若包含小括号,小括号内的字符组合会散布在分割后的子字符串之间。例如:

>>> re.split('o(ob)', 'foobar')['f', 'ob', 'ar']
findall

  下例是现在字符串内查找指定内容:

>>> pat = '[a-zA-Z]+' #查找单词>>> text = '"Hm... Err -- are you sure?" he said, sounding insecure.'>>> re.findall(pat, text)['Hm', 'Err', 'are', 'you', 'sure', 'he', 'said', 'sounding', 'insecure']>>> pat = r'[.?\-",]+' #查找标点符号,横线-被转义了>>> re.findall(pat, text)['"', '...', '--', '?"', ',', '.']
sub

  参见以下例子,详情参见本章后续 4.作为替换的组号和函数 部分:

>>> pat = '{name}'>>> text = 'Dear {name}...'>>> re.sub(pat, 'Mr. Gumby', text)'Dear Mr. Gumby...'
escape

  适用于一下情况:
  ♦ 字符串很长其包含很多特殊字符;
  ♦ 字符串来自于用户,且要用作正则表达式的一部分。
  下面例子演示函数的工作原理:

>>> re.escape('www.python.org')'www\\.python\\.org'>>> re.escape('But where is the ambiguity?')'But\\ where\\ is\\ the\\ ambiguity\\?'

3. 匹配对象和组

  组(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$’,可以取出’www.’和’.com’之间的内容。
  re匹配对象的一些重要方法,如下表所示:

方法 描述 group([group1, …]) 获取给定子模式(组)的匹配项 若未给定组号,默认组0;
若给定一个组号,返回单个字符串;
若给定多个组号,将对应给定组数的字符串作为元组返回 start([group]) 返回给定组的匹配项的开始位置 字符索引,默认为0 end([group]) 返回给定组的匹配项的结束位置(和分片一样,不包括组的结束位置) 字符索引+1 span([group]) 返回一个组的开始和结束位置

  NOTE:组号只能使用0~99。
  具体应用参照一下例子:

>>> m = re.match(r'www\.(.*)\..{3}', 'www.python.org')>>> m.group(1)'python'>>> m.start(1)4>>> m.end(1)10>>> m.span(1)(4, 10)

4. 作为替换的组号和函数

  见证re.sub强大功能的最简单方法就是在替换字符串中使用组号。在替换内容中以’\n’形式出现的任何转义序列都会被模式中的组n匹配的字符串替换掉。
  例如,假设要把’*something*’用’<em>something</em>’替换掉。

>>> emphasis_pattern = re.compile(r''' # 正则表达式通常难以理解,需要定义有意义的变量名...                               \*     # Beginning emphasis tag -- an asterisk...                               (      # Begin group for capturing phrase...                               [^\*]+ # Capture anything except asterisk...                               )      # End group...                               \*     # Ending emphasis tag...                               ''', re.VERBOSE) # 使用VERBOSE标志使正则表达式更易读>>> re.sub(emphasis_pattern, r'<em>\1</em>', 'Hello, *world*!')'Hello, <em>world</em>!'

  NOTE: re函数中使用VERBOSE标志,有以下优势:
  ♦ 允许模式中添加空白(空格、tab、换行符……),除非将其放在字符类或者转义;
  ♦ 可在冗长的正则式中添加注释

  将函数作为替换内容可以让替换功能变得更加强大。 MatchObject将作为函数的唯一参数,返回的字符串将会用做替换内容。换句话说,可以对匹配的子字符串做任何事,并可以细化处理过程,以生成替换内容。


贪婪和非贪婪模式

  重复运算符默认是贪婪(greedy)的,意味着会进行尽可能多的匹配。
  例如,上面替换的例子:

>>> emphasis_pattern = r'\*(.+)\*' # 匹配模式:星号+一个/多个字符+星号>>> re.sub(emphasis_pattern, r'<em>\1</em>', '*This* is *it*!')'<em>This* is *it</em>!' # 匹配了第一个星号和最后一个星号之间的所有内容。意味着运算符+是贪婪的。

  另一种情况:要求把’**something**’用’<em>something</em>’替换掉。
  为了避免贪婪,可以使用重复运算符的非贪婪版本(运算符后面添加问号),意味着会尽量少的匹配——在模式的结尾进行匹配。例如:

>>> emphasis_pattern = r'\*(.+?)\*'>>> re.sub(emphasis_pattern, r'<em>\1</em>', '*This* is *it*!')'<em>This</em> is <em>it</em>!'>>> emphasis_pattern = r'\*\*(.+?)\*\*'>>> re.sub(emphasis_pattern, r'<em>\1</em>', '**This** is **it**!')'<em>This</em> is <em>it</em>!'

5. 找出Email的发信人

  以处理Email为例,演示re模块的使用。下面文件message.eml是一封保存文本文件的为Email。

from foo@bar.baz thu dec 20 01:22:50 2008return-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 +0000user-agent: microsoft-outlook-express-macintosh-edition/5.02.2022date: wed, 19 dec 2008 17:22:42 -0700subject: re: spamfrom: 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: <20041219213308.a2655@bozz.floop>mime-version: 1.0content-type: text/plain: charset='US-ASCII'content-transfer-ending: 7bitstatus: rocontent-length: 55lines: 6So long, and thanks for all the spam!Yours,Foo Fie

  需求1:查找Email是谁发送的。
  
  从Email文本中可以看出,包含发信人的文本行义字符串’from: ‘作为开始,以防止在尖括号中的Email地址作为结束。二者之间的文本就是发信人。参照这些信息生成匹配模式,如下述代码:

# find_sender.pyimport fileinput, repat = re.compile('from: (.*) <.*?>$')for line in fileinput.input():  m = pat.match(line)  if m: print m.group(1)

  上述代码需要注意以下几点:
  ♦ 用compile函数处理正则表达式,让处理过程更有效率;
  ♦ 取出的子模式放在圆括号中作为组;
  ♦ 使用非贪婪模式对邮件地址进行匹配,避免名字中包含中括号;
  ♦ 使用美元符号表示匹配整行;
  ♦ 使用if语句确保已经匹配成功。
  执行结果如下:

# python find_sender.py messsage.eml foo fie

  需求2:查找头部信息中所有的Email地址。实现代码如下:

#find_address.pyimport fileinput, repat = re.compile(r'[a-z\.]+@[a-z\.]+', re.IGNORECASE) # 建立匹配Email地址的正则表达式addresses = set()for line in fileinput.input():  for address in pat.findall(line): # 使用findall找出每行出现的匹配项    addresses.add(address)          # 为了避免重复,将匹配结果放在集合中for address in sorted(addresses):   # 对所有匹配项进行排序  print address                     # 打印结果

  执行结果如下:

#python find_address.py messsage.eml foo@bar.bazfoo@baz.commagnus@bozz.floopmr.gumby@bar.baz

6. 模板系统示例

  模板是一种通过放入具体值从而得到某种已完成文本的文件。
  
  Python有一种高级的模板机制:字符串格式化。比如以下需求:
  ♦ 需要把’[something]’(字段)的匹配项替换为通过Python表达式计算出来的something结果。例如’This 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将求值结果替换为处理后的字符串。

  下面代码实现一个简单的模板:

# templates.pyimport fileinput, re# 匹配中括号内的字段field_pat = re.compile(r'\[(.+?)\]')# 创建充当模板作用域的字典scope = {}#用于re.sub的替换函数def replacement(match):  code = match.group(1)  try:    # 若字段可以求值,则返回它    return str(eval(code, scope))  except SyntaxError:    # 否则执行相同作用域内的赋值语句……    exec code in scope    # ……返回空字符串    return ''lines = []for line in fileinput.input():  lines.append(line)text = ''.join(lines)print field_pat.sub(replacement, text)

  上面例子中使用了fileinput,可以轮流处理接个文件。这里将变量定义和插入变量的模板作为两个独立文件进行处理,分别命名为magnus.txt和template.txt,具体内容如下:
  
  magnus.txt

[name     = 'Magnus Lie Hetland'][email    = 'mangnus@foo.bar'   ][language = 'python'            ]

  template.txt 

import time]Dear [name].I would like to learn how to program. I hear you usethe [language] language a lot -- is it something Ishould consider?And, by the way, is [email] your correct email address?Fooville, [time.asctime()]Oscar Frozzbozz

  按以下方式运行,查看输出结果:

# python templates.py magnus.txt template.txt Dear Magnus Lie Hetland.I would like to learn how to program. I hear you usethe python language a lot -- is it something Ishould consider?And, by the way, is mangnus@foo.bar your correct email address?Fooville, Thu Dec 21 14:38:23 2017Oscar Frozzbozz

  上述代码还有以下改进空间:
  ♦ 可以用更灵活的方式来编写定义文件;
  ♦ 使用execfile执行文件,就可以使用正常的Python语法,也能解决输出内容中顶部出现空行的问题。

10.3.9. 其他有趣的标准模块

  本章涵盖的内容只是Python标准库的冰山一角。下面介绍一些其他库。

库 说明 functools 可以从这个库中找到一些功能,能够通过部分参数来使用某个函数(部分求值),稍后再为剩下的参数提供数值。
Python3.0中,filter和reduce包含在该模块中。 difflib 计算两个序列的相似程度。
可以从一些序列中找到和提供的原始序列“最像”的那个。
可用于创建简单的搜索程序。 hashlib hashlib模块可以通过字符串计算小“签名”(数字)。
可以确保使用两个字符串计算出的签名完全不同。
可用于大文本文件、加密和安全性等方面。 csv csv是逗号分隔符(Comma-Separated Values)的简写,是一种用于存储表格式数据的简单格式。
主要用于不同程序间数据交换。
csv模块可以轻松读写CSV文件。 timeit/profile/trace timeit模块:(以及他的命令行脚本)衡量代码片段运行时间。功能神秘,可代替time模块进行性能测试。
profile模块:(以及伴随模块pstats)可用于代码片段效率的全面分析。
trace模块:(和程序)可提供总的分析(即代码哪些部分执行) datetime 扩展time模块的功能,接口更直观。
支持特殊日期和时间对象,能以多种方式对他们进行构建和联合 itertools 模块有很多工具用来创建和联合迭代器,以及实现以下功能的函数:将可迭代的对象链接起来、创建返回无限连续整数的迭代器,从而实现重复访问可迭代对象进行循环。 logging 模块提供一组标准工具,以便让开发人员管理一个或者多个核心的日志文件,同时还对日志信息提供了多层次的优先级。 getopt/optparse 处理命令行的选项或开关。 cmd 模块可以用于编写命令行解释器,类似Python交互解释器。
可以自定义命令,让用户通过提示符来执行。
可将其作为程序的用户界面。