Python的标准安装包括一组模块,称为标准库。
10.1 模块
>>>emport math
>>>math.sin(0)
0.0
10.1.1 模块是程序
任何python程序都可以作为模块导入。
#hello.py
print "hello,world!"
解释器在哪里寻找模块。(windows)
>>>import sys
>>>sys.path.append('c:/python')
在unix系统中,不能只简单将字符串‘~/python’添加到sys.path中,必须使用完整路径。如果你希望将这个操作自动化,可以使用sys.path.expanduser('~/python')
完成这样之后就能导入自己的模块了
>>>import hello
hello,world!
在导入模块的时候,你可能会看到有新文件出现-----在本例中是c:\python\hello.pyc。这个是以.pyc为扩展名的文件是经过处理的,已经转换成python能够更加有效地处理的文件。如果稍后导入同一个模块,python会导入.pyc文件而不是.py文件,除非.py文件已改变----在这种情况下,会生成新的.pyc文件。删除.pyc文件不会损害程序---必要的时候创建新的。
要重新载入hello模块:
>>>hello = reload(hello)
hello,world!
python3.0已经去掉了reload函数。尽管用exec能够实现同样的功能,尽可能避免重新载入模块。
10.1.2 模块用于定义
1.在模块中定义函数
#hello2.py
def hello():
print "hello,world!"
>>>import hello2
模块会被执行,这意味着hello函数在模块的作用域被定义了,访问函数:
>>>hello2.hello()
hello,world!
为了让代码可重用,请将它模块话。
2.在模块中增加测试代码
#hello3.py
def hello():
print "hello,world!"
#A test:
hello()
告知模块本身是作为程序运行还是导入其他程序。为了实现这一点,需要使用__name__变量:
>>>__name__
'__main__'
>>>hello3.__name__
'hello3'
主程序中,变量__name__的值是‘__main__’.而在导入的模块中,这个值就被设定为模块的名字。因此,为了让模块的测试代码更加好用,可以将其放置在if语句中。
#hello4.py
def hello():
print "hello,world!"
def test():
hello()
if __name__ == '__main__': test()
如果将它作为程序运行,hello函数会被执行。而作为函数会被执行。而作为模块导入时,它的行为就会像普通模块一样:
>>>import hello4
>>>hello4.hello()
hello,world!
将测试代码放入独立的test函数会更灵活,这样做即使再把模块导入其他程序之后,仍然可以对其进行测试:
>>>hello4.test()
hello,world!
10.1.3 让你的模块可用
1.让模块放置在正确位置
>>>import sys,pprint
>>>pprint.pprint(sys.path)
列出放置的路径
sysy.path 找到site-packages目录是最佳选择
2.告诉编译器去×××
对以下不适用:
不希望自己的模块填满python解释器的目录
没有在python解释器目录中存储文件的权限
想将模块放在其他地方
标准的实现方法是在PYTHONPATH环境变量中包含模块所在的目录。
10.1.4 包
为了组织好模块,你可以将它们分组为包。包基本上就是另外一类模块,有趣的地方就是他们能包含其他模块。当模块存储在文件中时,包就是模块所在的目录。为了让python将其作为包对待,它必须包含一个命名为__init_py的文件。如果将它作为普通模块导入话,文件的内容就是包的内容。
比如有个名为constants的包,文件constants/__init___.py包括语句PI=3.14,那么:
import constants
print constants.PI
为了将模块放置在包内,直接把模块放在包目录内即可。
比如,要建立一个drawing的包,其中包括名为shapes和colors的模块,你就需要下面所需文件和目录:
文件/目录 描述
~/python/ PYTHONPATH中德目录
~/python/drawing/ 包目录
~/python/drawing/__init__.py 包代码
~/python/drawing/colors.py colors模块
~/python/drawing/shapes.py shapes模块
上面设置完,下面的语句都是合法的
import drawing
import drawing.colors
from drawing import shapes
10.2 探究模块
10.2.1 模块中有什么
1.dir 查看模块包含的内容可以使用dir函数,它会将对象的所有特性(以及模块的所有函数,类,变量等)列出。一些名字以下划线开始--暗示他们并不是在模块外部使用而准备的。所以用列表推导式过滤掉它们:
>>>[n for n in dir(copy) if not n.startswith('_')]
['Error','PyStringMap','copy','deepcopy','dispatch_table','error','name','t']
这个列表推导式是个包含dir(copy)中所有不以下划线开头的名字的列表。
2.__all__变量
>>>copy.__all__
['Error','copy','deepcopy']
它定义了模块的公有接口。
from copy import *
那么,只能使用__all__变量中的4个函数,要导入‘t’的话,就得显示的实现或者导入copy然后使用copy.t,或者from copy import t。
因为模块有一大堆,所以__all__设置可以过滤掉一些不用的。
10.2.2 用help获取帮助
>>>help(copy.copy)
解释
>>>print copy.copy.__doc__
10.2.3 文档
>>>print range.__doc__
可以查看关于range函数的精确描述。
>>>print copy.__file__
查看模块属性
10.3 标准库
10.3.1 sys
sys这个模块让你能够访问与python解释器联系紧密的变量和函数。
反序打印命令行参数
#reverseargs.py
import sys
args = sys.argv[1:]
args.reverse()
print ' '.join(args)
或者
[print ' '.join(reversed(sys.argv[1:])) ]
$python reverseargs.py this is a test
test a is this
10.3.2 os
os模块为你提供了访问多个操作系统服务的功能。
例如
os.system('/usr/bin/firefox')
10.3.3 fileinput
可以轻松的遍历文本文件的所有行。
$python some_script.py file1.txt file2.txt
这样可以依次遍历所有的文件
$cat file.txt | python some_script.py
为python脚本添加行号
#numberlines.py
import fileinput
for line fileinput.input(inplace=True):
line = line.rstrip()
num = fileinput.lineno()
print '%-40s # %2i' % (line,num)
$python numberlines.py numberlines.py
输出
#numberlines.py #1
import fileinput #2
for line fileinput.input(inplace=True): #3
line = line.rstrip() #4
num = fileinput.lineno() #5
print '%-40s # %2i' % (line,num) #6
10.3.4 集合、堆和双端队列
1.集合
>>>set(range(10))
set([0,1,2,3,4,5,6,7,8,9])
集合是由序列构建的。它们主要用于检查成员资格,因此副本时被忽略的:
>>>set([0,1,2,3,0,1,2,3,4,5])
set([0,1,2,3,4,5])
和字典一样,集合元素的顺序是随意的,因此我们不应该以元素的顺序作为依据进行编程:
>>>set(['fee','fie','foe'])
set(['foe','fee','fie'])
除了检查成员资格外,还可以使用标准的集合操作,比如求并集和交集,可以使用方法,也可以使用对整数进行位操作时使用的操作。比如想要找出两个集合的并集,可以使用其中一个集合的union方法或者使用按位与运算符;
>>>a = set([1,2,3])
>>>b = set([2,3,4])
>>>a.union(b)
set([1,2,3,4])
>>>a | b
set([1,2,3,4])
>>>c = a & b
>>>c.issubset(a)
True
>>>c <= a
True
>>>c.issuperset(a)
False
>>>c >= a
False
>>>a.intersection(b)
set([2,3])
>>>a & b
set([2,3])
>>>a.difference(b)
set([1])
>>>a - b
set([1])
>>>a.symetric_difference(b)
set([1,4])
>>> a ^ b
set([1,4])
>>>a.copy()
set([1,2,3])
>>>a.copy() is a
False
如果需要一个函数,用于查找并且打印两个集合的并集,可以使用来自set类型的union方法的未绑定版本,这种做法很有用,比如结合reduce来使用:
>>>mySets = []
>>>for i in range(10):
mySets.append(set(range(i,i+5)))
>>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)
报错
>>>a.add(frozenset(b))
frozenset构造函数创建给定集合的副本,不管是将集合作为其他集合成员还是字典的键,frozenset都很有用。
2.堆
python中并没有独立的堆类型---只有一个包含一些堆操作函数的模块,这个模块叫做heapq。
heappush函数用于增加堆得项。注意,不能将它用于任何之前讲述的列表中---它只能用于通过各种堆函数建立的列表中。原因时元素的顺序很重要(尽管看起来是随意排列,元素并不是进行严格排序的)。
>>>from heapq import *
>>>from random import shuffle
>>>date = 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位置处的元素大,这是底层堆算法的基础,而这个特性成为堆属性。
heappop函数弹出最小的元素---一般来说都是在索引0处的元素,并且会确保剩余元素中最小的那个占据这个位置。一般来说,尽管弹出列表的第一个元素并不是很有效率,但是这里不是问题,因为heappop会做一些精巧的移位操作:
>>>heappop(heap)
0
>>>heappop(heap)
0.5
>>>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大或第n小的元素。你可以使用排序和分片来完成这个工作,但是堆算法更快而且更有效的使用内存。
3.双端队列(以及其他集合类型)
双端队列在需要按照元素增加的顺序来移除元素时非常有用。
双端队列通过可迭代对象创建,而且有些非常有用的方法。
>>>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和列表的extend方法差不多,extendleft则类似于appendleft。注意,extendleft使用的可迭代对象中的元素会反序出现在双端队列中。
10.3.5 time
time模块所包括的函数能够实现以下功能:获得当前时间、操作时间和日期、从字符串读取时间以及格式化时间为字符串。比如,元组:
(2008,1,21,12,2,56,0,21,0)
年 月 日 时 分 秒 周 儒历日 夏令时
秒的范围是0~61是为了应付闰秒和双闰秒。夏令时的数字是布尔值,但是如果使用了-1,mktime就会工作正常。
>>>time.asctime()
'Fri Dec 21 05:41:27 2008'
10.3.6 random
random模块包括返回随机数的函数,可以用于模拟或者用于任何产生随机输出的程序。
from random import *
from time import *
date1 = (2008,1,1,0,0,0,-1,-1,-1)
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))
Mon Jun 24 21:35:19 2008
接下来,我们要求用户选择投掷的色子数以及每个色子具有的面数。投色子机制可以由randrange和for循环实现:
from random import randrange
num = input('How many dice?')
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
接下来假设有一个新建的文本文件,它的每一行文本都代表一种运势,那么我们就可以使用前面介绍的fileinput模块将“运势”都存入列表中,再进行随机选择:
#fortune.py
import fileinput ,random
fortunes = list(fileinput.input())
print random.choice(fortunes)
在UNIX中,可以对标准字典文件/usr/dict/words进行测试,以获得一个随机单词:
$python fortune.py /usr/dict/words
dodge
最后一个例子,假设你希望程序能够在每次敲击回车的时候都为自己发一张牌,同时还要确保不会获得相同的牌,首先要创建“一副牌”-------字符串列表:
>>>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
............]
>>>from random import shuffle
>>>shuffle(deck)
>>>pprint(deck[:12])
['3 of spades',
'2 of diamonds'
.............]
最后,为了让python在每次按回车的时候都给你发一张牌,直到发完为止,那么只需要创建一个小的while循环即可。假设将建立牌的代码放在程序文件中,那么只需要在程序的结尾处加入下面这行代码:
while deck:raw_input(deck.pop())
10.3.7 shelve
如何在文件中存储数据,只需要一个简单的存储方案,shelve模块就可以。
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对象中查找元素的时候,这个对象都会根据已经存储的版本进行重新构建,当你将元素赋给某个键的时候,它就被存储了。
1.列表['a','b','c']存储在键x下;
2.获得存储的表示,并且根据它来创建新的列表,而‘d’被添加到这个副本中,修改的版本还没被保存。
3.最终,再次获得原始版本-------没有‘d’
为了正确的使用shelve模块修改存储的对象,必须将临时变量绑定到获得的副本上,并且在它被修改后重新存储这个副本:
>>>temp = s['x']
>>>temp.append('d')
>>>s['x'] = temp
>>>s['x']
['a','b','c','d']
将open函数的writeback参数设为true,如果这样做,所有从shelf读取或者赋值到shelf的数据结构都会保存在内存中,并且只有在关闭shelf的时候才写回到磁盘中。如果处理的数据不大,并且不想考虑这些问题,那么将writeback设为true的方法还是不错的。
2.简单的数据库示例
#database.py
import sys.shelve
def store_person(db):
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] = person
def lookup_person(db):
pid = raw_input('Enter ID number:')
filed = raw_input('What would you like to know?(name,age,phone)')
filed = filed.strip().lower
print filed.capitalize() + ':',\
db[pid][filed]
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 = raw_input('Enter command (? for help):')
cmd = cmd.strip().lower()
return cmd
def main():
database = shelve.open('C:\\database.bat')
try:
while True:
cmd = enter_command()
if cmd == 'store':
store_person(database)
elif cmd == 'lookup':
lookup_person(database)
slif cmd == '?'
print_help()
slif cmd == 'quit'
return
finally:
database.close()
if __name__ == '__main__':main()
10.3.8 re
re模块包含正则表达式的支持。
‘python\\.org’ 或者 r'python\.org'
>>>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']
函数re.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)
['"','...','--','?"',',','.']
注意,横线被转义了,所以python不会将其解释为字符范围的一部分。
函数re.sub的作用在于:使用给定的替换内容将匹配模式的子字符串替换掉。
>>>pat = '{name}'
>>>text = 'Dear {name}...'
>>>re.sub(pat,'Mr. Gumby',text)
'Dear Mr. Gumby'
re.escape是一个很实用的函数,它可以对字符串中所有可能被解释为正则运算符的字符进行转义的应用函数。如果字符串很长且包含很多特殊字符,而你又不想输入一大堆反斜线,或者字符串来自于用户,且要用作正则表达式的一部分的时候,可以使用这个函数。下面的例子向你演示了该函数是如何工作的:
>>>re.escape('www.python.org')
'www\\.python\\.org'
>>>re.escape('But where is the ambiguity?')
'But\\ where\\ is\\ the\\ ambiguity\\?'
3. 匹配对象和组
对于re模块中那些能够对字符串进行模式匹配的函数而言,当能找到匹配项的时候,它们都会返回MatchObject对象。这些对象包括匹配模式的子字符串的信息。它们还包含了哪个模式匹配了子字符串哪部分的信息----这些“部分”叫做组。
组就是放置在圆括号内的子模式。组的序号取决于它左侧的括号数。组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’之间的所有内容。像这样创建模式的话,就可以取出字符串中感兴趣的部分了。
除了整体匹配外,我们只能使用1~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.作为替换的组号和函数
'*something*' 用 'something' 替换掉
>>>emphasis_pattern = r'\*([^\*]+)\*'
让正则表达式更易读是在re函数中使用VERBOSE标志。
>>>emphasis_pattern = re.compile(r'''
\* #解释
( #...
[^\*]+ #...
) #...
\* #...
''',re.VERBOSE)
现在模式已经搞定,接下来就可以使用re.sub进行替换了:
>>>re.sub(emphasis_pattern,r'\1','Hello,*world*!')
'Hello,world!'
贪婪和非贪婪模式
>>>emphasis_pattern = r'\*(.+)\*'
>>>re.sub(emphasis_pattern,r'\1','*This* is *it*!')
'This* is *it!'
>>>emphasis_pattern = r'\*\*(.+?)\*\*'
>>>re.sub(emphasis_pattern,r'\1','**This** is **it**!')
'This is it!'
这里用+?运算符代替了+,意味着模式会像之前那样对一个或者多个通配符进行匹配,但是它是尽可能少的匹配,因为是非贪婪的。
5.模版系统示例
模板是一种通过放入具体值从而得到某种已完成文本的文件。比如,你可能会有只需要插入收件人姓名的邮件模版。python有一种高级的模版机制:字符串格式化。但是使用正则表达式可以让系统更加高级。假设需要把所有'[somethings]'的匹配项替换为通过python表达式计算出来的something结果,所以下面的字符串:
'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'
下面是简单实现:
#templates.py
import fileinput,re
#匹配中括号里的字段:
filed_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)
#filed模式的所有匹配项都替换掉:
print field_pat.sub(replacement,text)
简单来说,程序做了下面的事情:
定义了用于匹配字段的模式
创建充当模版作用域的字典
定义具有下列功能的替换函数
将组1从匹配中取出,放入code中:
通过将作用域字典作为命名空间来对code进行求值,将结果转换为字符串返回,如果成 功的话,字段就是个表达式,一切正常。否则,跳到下一步。
执行在相同命名空间内的字段来对表达式求值,返回空字符串
使用fileinput读取所有可用的行,将其放入列表,组合成一个大字符串。
将所有field_pat匹配项用re.sub中的替换函数进行替换,并且打印结果。