randrange([start], stop, [step]) #返回range(start,stop, step)中的随机数
randint([start], stop, [step]) #调用的是randrange(start, stop+1),所以包含上限。
choice(seq) #从序列seq中返回随意元素
shuffle(seq[, random]) #原地指定序列seq——将给定(可变)序列的元素进行随机移位,每种排列的可能性都是近似相等的
sample(seq, n) #从序列seq中选择n个随机且独立的元素(元素互不相同)
标准函数random.randrange能产生范围内的随机整数,如想获得1—10(包括10)的随机数,可使用randrange(1, 11)(或者使用randrange(10)+1),如果想获得小于20的随机正奇数,可使用randrange(1, 20, 2)
一些例子(使用一些time模块中的函数):
#首先获得代表时间间隔(2008年这一年)限制的实数
from random import *
from time import *
date1 = (2008, 1, 1, 0, 0, 0, -1, -1, -1) #时间元组的方式来表示日期,使用-1表示一周中的某天、一年中的某天和夏时令,以便Python自行计算
time1 = mktime(date1) #对元组调用mktime
date2 = (2009, 1, 1, 0, 0, 0, -1, -1, -1)
time2 = mktime(date2)
>>> random_time = uniform(time1, time2) #在这一范围内均一的生成随机数(不包括上限)
>>> print asctime(localtime(random_time)) #将数字转化为易读的日期形式
Sun Jul 06 06:12:48 2008
#2 要求用户选择投掷的骰子数以及每个骰子具有的面数。投骰子机制可由randrange和for循环实现
from random import randrange
num = input("how many dice? ")
sides = input("How many sides per die? ")
sum = 0
for i in range(num): sum += randrange(sides)+1
print "The resurt is", sum
执行:
How many dice? 3
How many sides per die? 6
The result is 10
#3 假设有一个新建的文本文件,它的每一行代表一种运势。可使用fileinput模块将“运势”都存入列表,在进行随机选择:
#文件名 fortune.py
import fileinput, random
fortunes = list(fileinput.input())
print random.choice(fortunes)
#4 希望程序在每次敲击回车的时候都为自己发一张牌,同时还要确保不会获得相同的牌。
#首先创建一副牌 ——字符串列表
>>>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])
['8 of diamonds',
'5 of spades',
'4 of spades',
'3 of diamonds',
'7 of hearts',
'5 of diamonds',
'Jack of clubs',
'King of hearts',
'3 of spades',
'4 of diamonds',
'2 of spades',
'Queen of diamonds']
#让Python在每次按回车的时候都给你发一张牌,直到发完为止,那只需在程序后放一个小的while循环即可。
while deck: raw_input(deck.pop())
10.3.7 shelve
若只需要一个简单的存储方案,那shelve模块可满足大部分的要求,只需为它提供文件名。shelve中唯一有趣的函数是open,在调用它的时候(使用文件名作为参数),它会返回一个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']
'd'去哪了?—— 当你在shelf对象中查找元素的时候,这个对象都会根据已经存储的版本进行重新构建,当你将元素赋给某个键时,它就被存储了。
为正确使用shelve模块修改存储对象,必须将临时变量绑定到获得的副本上,并且在它被修改后重新存储这个副本:
>>> temp = s['x']
>>> temp.append('d')
>>> s['x'] = temp
>>> s['x']
['a', 'b', 'c', 'd']
Python2.4之后的版本还有个解决办法:将open函数的writeback参数设为True。这样,所有从shelf读取或赋值到shelf的数据结构都会被保存在内存(缓存)中,并且只有在关闭shelf的时候才写回到磁盘中。若处理的数据不大,且不想考虑这些问题,那将writeback设为true(确保在最后关闭了shelf)的方法还是不错的。
2 简单的数据库示例
#简单的使用shelve模块的数据库应用程序。
#代码清单10-8 简单的数据库应用程序
#database.py
import sys, shelve
def 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] = person
def lookup_person(db):
"""
Query user for ID and desired field, and fetch the corresponding data 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() + ':', \ #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 '? : Print this message'
def enter_command():
cmd = raw_input('Enter command (? for help): ')
cmd = cmd.strip().lower()
return cmd
def main(): #主程序main,只有在if __name__ =='__main__'条件成立时才被调用。
database = shelve.open('C:\\database.dat') #you may want to change this name #打开数据库(shelf),将其作为参数传递给其他需要他的函数。
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() #意味着可以在其他程序中将这个程序作为模块导入,然后调用main函数
简单的交互过程:
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
? : Print 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
10.3.8 re
re模块包含对正则表达书(regular expression)的支持。学习它们的关键是一次只学习一点—— (在文档中)查找满足特定任务需要的那部分内容,预先将它们全部记住是没有必要的。本节会对re模块主要特征和正则表达式进行介绍。
1 什么是正则表达式
正则表达式是可以匹配文本片段的模式。最简单的正则表达式就是普通字符串,可匹配其自身。正则表达式‘python’可匹配字符串‘python’。你可以用这种匹配行为搜索文本中的模式,并且用计算后的值替换特定模式,或将文本进行分段。
#通配符(点号)——可匹配“任何字符串”(除换行符外的任何单个字符)
正则表达式可以匹配多于一个的字符串,可使用一些特殊字符创建这类模式。如:点号(.)可匹配任何字符(除了换行符),所以正则表达式‘.ython’可匹配字符串‘python’和‘jython’。还能匹配‘qython’、‘+ython’或者‘ ython’,但是不会匹配‘cpython’或者‘ython’这样的字符串,因为
点号只能匹配一个字母,而不是两个或零个。
#对特殊字符进行转义
在正则表达式中如果将特殊字符作为普通字符使用就会 遇到问题。假设需匹配字符串‘python.org’,直接使用‘python.org’可以吗?可以的,但是这样也会匹配‘pythonzorg’。为了让特殊字符表现的像普通字符一样,需要对它进行转义(escape)——在其前面加上反斜线。所以‘python\\.org’就只会匹配‘python.org’
#字符集
有时你需要更多的控制权,可使用中括号括住字符串来创建字符集(character set)。字符集可匹配它所包含的任意字符,所以‘[py]thon’能够匹配‘python’和‘jython’,而非其他内容。可使用范围,如‘[a-z]’能够(按字母顺序)匹配a到z的任意一个字符,还可通过一个接一个的方式将范围联合起来使用,如:‘[a-zA-Z0-9]’能匹配任意大小写字母和数字(注意字符集只能匹配一个这样的字符)。
为
反
转字符集,可在开头使用^字符,比如‘[^abc]’可匹配除a、b、c之外的字符。
#字符集中的特殊字符
一般,若希望点号、星号和问号等特殊字符
在模式中
用作文本字符而不是正则表达式运算符,那
需要用反斜线进行转义
。在字符集中,对这些字符进行转义通常没必要(但完全合法)。但是由下列规则需记住:
如果脱字符(^)出现在字符集开头,那就需要对其进行转义(除非希望其用作否定运算符)
右中括号(])和横线(-)应放在字符集的开头或用反斜线转义(事实,如果需要,横线也能放在末尾)
#选择符和子模式
在字符串的每个字符都各不相同的情况下,字符集很好用,但如果只想匹配字符串‘python’和‘perl’,就不能使用字符集或者通配符来指定某个特定模式了。应该使用用于选择项的特殊字符:管道字符(|)。因此所需模式写为:‘python|perl’。
有时不需要对整个模式使用选择运算符——只是模式的一部分。可使用圆括号扩起需要的部分,或称子模式(subparttern)。前例可写为‘p(ython|erl)’
#可选项和重复子模式
在子模式后加问号,就变成了可选项。可能出现在匹配字符串中,但并非必须。如下面这个模式:
r'(http://)?(www\.)?python\.org'
只能匹配下列字符串:
‘http://www.python.org’
'http://python.org'
'www.python.org'
'python.org'
上面模式,值得注意的地方:
对点号进行了转义,防止它被作为通配符使用;
使用原始字符串减少所需反斜线的数量;
每个可选子模式都用圆括号括起;
可选子模式出现与否均可而且相互独立。
问号表示子模式可出现一次或零次。下面这些字符串允许子模式重复多次:
(pattern*):允许模式重复0次或多次;
(pattern*): 允许模式重复1次或多次;
(pattern*){m,n}:允许模式重复m~n次。
#字符串的开始和结尾
也能寻找匹配模式的子字符串,如:字符串‘www.python.org’中的子字符串‘www’能匹配模式'w+'。在寻找这样的子字符串时,确定子字符串位于整个字符串的开始开始结尾是很有用的。如:只想在字符串的开头而不是其他位置匹配‘ht+p’,那就可
使用脱字符(^)标记开始:‘^ht+p’会匹配‘http://python.org’及(‘htttttp://python.org’),但是不匹配‘www.http.org’。类似的,字符串结尾用美元符号($)标识。
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) #将字符串中所有特殊正则表达式字符转义
函数re.compile将正则表达式(以字符串书写的)转换为模式对象,可实现更有效率的匹配。
函数re.search会在给定字符串中寻找第一个匹配给定正则表达式的子字符串。一但找到子字符串,函数就返回MatchObject(True),否则返回None(False),所以该函数可用在条件语句中:
if re.search(pat, string):
print 'Found it!'
函数re.match会在给定字符串的开头匹配正则表达式。所以,match('p', 'python')返回真;而re.match('p', 'www.python.org')返回假。
函数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'
函数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'[.?\-",]+' #横线被转义,python不会将其解释为字符范围的一部分
>>> re.findall(pat, text)
['"', '...', '---', '?"', ',', '.']
函数re.sub的作用:使用给定的替换内容将匹配模式的子字符串(最左端并且非重叠的子字符串)替换掉。
>>> pat = '{name}'
>>> text = 'Dear {name}...'
>>> re.sub(pat, 'Mr Gumby', text)
'Dear Mr Gumby...'
re.escape是一个很有用的函数,可对字符串中所有可能被解释为正则运算符的字符进行转义的应用函数。当字符串很长且包含很多特殊字符,且不想输入一大堆反斜线;或字符串来自用户(如通过raw_input函数获取的内容),且要用作正则表达式的一部分时。可使用这个函数。
>>> re.escape('www.python.org')
'www\\.python\\.org'
>>> re.escape('But where is the ambiguity?')
'But\\ where\\ is\\ the\\ ambiguity\\?'
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'之间的所有内容。像这样创建模式的话,就可以取出字符串中最感兴趣的部分了。
re匹配对象的一些重要方法:
group([group1, ...]) #获取给定子模式(组)的匹配项
start([group]) #返回给定组的匹配项的开始位置(默认为0,即整个模式)
end([group]) #返回给定组的匹配项的结束位置(不包括组的结束位置,返回结果是结束索引+1)
span([group]) #返回一个组的开始和结束位置(以元组(start,end)的形式返回给定组的开始和结束位置的索引(默认为0,即整个模式))
思考下面例子:
>>> 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的第一个例子中,只把一个字符串用其他内容替换掉了。用replace字符串方法能达到同样的效果。但是,正则表达式允许以更灵活的方式搜索,同时也允许进行功能更强大的替换。
体现re.sub强大功能的最简单方式就是在替换字符串中使用组号。在替换内容中以‘\\n’形式出现的任何转义序列都会被模式中与组n匹配的字符串替换掉。如:假设要把‘*something*’用‘something’替换掉,前者是普通文本文档(如Email)中进行强调的常见方法,后者是相应的HTML代码(用于网页)。
首先建立正则表达式:
>>> emphasis_pattern = r'\*([^\*]+)\*'
需注意的是:正则表达式很容易变得难以理解,所以为了让其他人(包括自己)在以后能读懂代码,使用有意义的变量名(或加上一两句注释)是很重要的。让正则表达式更易读的方式是在re函数中使用VERBOSE标志——允许在模式中添加空白(空白字符、tab、换行符、等等),re会忽略他们(除非将其放在字符类或者用反斜线转义)。也可在冗长的正则式中添加注释。
>>>emphasis_pattern = re.compile(r'''
... \* #Beginning emphasis tag -- an asterisk
... ( #Begin group for capturing phrase
... [^\*]+ #Capture anything except asterisks
... ) #End group
... \* #Ending emphasis tag
... ''', re.VERBOSE)
...
接下来可使用re.sub进行替换:
>>> re.sub(emphasis_pattern, r'\1', 'Hello, *world*!')
'Hello, world!
将函数作为替换内容可让替换功能变得更加强大。MatchObject将作为函数的唯一参数,返回的字符串将会作为替换内容。换句话说,可以对匹配的子字符串做任何事,并且可以细化处理过程,以生成替换内容。正则表达式可有无数的应用。
贪婪和非贪婪模式
重复运算符默认是贪婪(greedy)的—会进行尽可能多的匹配。如,假设重写了刚才用到的程序,以使用下面的模式:
>>> emphasis_pattern = r'\*(.+)\*' #会匹配:星号加上一个或多个字符,再加上一个星号的字符串。
>>> re.sub(emphasis_pattern, r'\1', '*This* is *it*!')
'This* is *it!' #模式匹配了从开始星号到结束星号之间的所有内容——包括中间的两个星号:贪婪的—将尽可能多的东西据为己有。
当你知道某个特定字母不合法的时候,前面的解决方案(使用字符集匹配任何不是星号的内容[^\*]+)才是可行的。但是,如果使用‘**something**’表示强调呢?现在所强调部分包括单个星号已经不是问题了,但是如何避免过于贪婪?
很简单,只需使用重复运算符的非贪婪版本(在其后面加上一个问号):
>>> emphasis_pattern = r'\*\*(.+?)\*\*' #+?替换了+,模式还会像原来那样对一个或多个通配符进行匹配,但会进行尽可能少匹配,是非贪婪的。
>>> re.sub(emphasis_pattern, r'\1', '**This** is **it**!')
'This is it!'
5. 找出Email的发件人
将Email存为文本文件,文件的头部包含了一大堆与邮件内容无关的信息,如代码清单10-9所示
#代码清单10-9 一组(虚构的)Email头部信息
Return_Path:
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 : 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
To: Magnus Lie Hetland
CC:
Message-ID:
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
#试着找出这封邮件是谁发的(如果直接看邮件可直接指出),找出通用模式。
#怎么把发件人的名字取出而不带Email地址?或如何将头部信息中包含的Email地址列示出来?
#代码清单10-10 寻找发件人的程序
#首先找出发件人:包含发件人的文本行以字符串“From: ”作为开始,以放置在尖括号(<>)中的Email中的地址为结束。我们需要的文本夹杂中间。
#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)
10.3.9 其他有趣的标准模块
10.4 小结
本章主讲了模块的知识:如何创建、如何探究及如何使用python的标准库中的模块。
模块: 从基本上来说,模块就是子程序,它的主函数则用于定义(定义函数、类和变量)。若模块包含了测试代码,那应该将这部分代码放置在检查__name__=='__main__'是否为真的if语句中。能够在PYTHONPATH中找到的模块都可以导入。语句import foo 可以导入存储在foo.py文件中的模块。
包: 包是包含在其他模块中的模块。包是作为包含__init__.py文件的目录来实现的。
探究模块:将模块导入交互式编辑器后,可使用很多方法对其进行探究。如使用dir、检查__all__变量及使用help函数。文档和源代码是获取信息和内部机制的极好来源。
标准库:python包含了一些模块,总称标准库。
本章介绍了其中很多模块。
10.4.1 本章新函数
dir(obj) #返回按字母顺序排列的属性名称列表
help([obj]) #提供交互式帮助或关于特定对象的交互式帮助信息
reload(module) #返回已经导入模块的重新载入版本,在python3.0中废除?