learnpython09.docx
《learnpython09.docx》由会员分享,可在线阅读,更多相关《learnpython09.docx(57页珍藏版)》请在冰豆网上搜索。
![learnpython09.docx](https://file1.bdocx.com/fileroot1/2023-2/21/62064340-08ff-44f6-8b6b-7559a3e7c341/62064340-08ff-44f6-8b6b-7559a3e7c3411.gif)
learnpython09
272
第九章
用Python完成
常见的任务
本章内容:
●数据结构操作
●文件操作
●操作程序
●与Internet相关的任务
●较大的例子
●练习
现在,我们已学习了Python的语法,它的基本的数据类型,和很多我们喜欢的
Python的库函数。
本章假设你至少理解了这门语言的所有基本成分,并且除了
Python的优雅和“酷”的方面外,也了解了它实用的方面。
我们将介绍Python程
序员要面对的常见的任务。
这些任务分为——数据结构操作,文件操作等等。
数据结构操作
Python的最大的特点之一是它把列表、元组和字典作为内置类型。
它们非常灵活
和容易使用,一旦你开始使用它们,你将发现你会不由自主地想到它们。
内嵌(inline)拷贝
由于Python引用的管理模式,语句a=b并没有对b引用的对象作拷贝,而只
是对那个对象产生了新的引用。
有时需要一个对象的新的拷贝,而不只是共享一
个引用。
怎样做到这一点依赖于对象的类型。
拷贝列表和元组的最简单的方式有
点奇怪。
如果myList是一个列表,那么要对它做拷贝,你可以用:
newList=myList[:
]
你可以理解为“从开始到结尾的分片”,因为我们在第二章“类型和操作符”里学
用Python完成常见的任务
273
到,一个分片开始的缺省索引是序列的开始(0),而缺省的结尾是序列的结尾。
由于元组支持同样的分片操作,这个技术也适用于拷贝元组。
而字典却不支持分
片操作。
为了拷贝字典myDict,你可以用:
newDict={}
forkeyinmyDict.keys():
newDict[key]=myDict[key]
这个操作很常见,所以在Python1.5里为字典对象增加了一个新方法来完成这个
任务,就是copy()方法。
所以前面的代码可以替换为一句话:
newDict=myDict.copy()
另一个常见的字典操作现在也是标准的字典特性了。
如果你有一个字典oneDict,
而想用另一个不同的字典otherDict的内容替换它,只需要用:
oneDict.update(otherDict)
这与下面的代码相同:
forkeyinotherDict.keys():
oneDict[key]=otherDict[key]
如果在update()操作前oneDict与otherDict共享一些键时,在oneDict中的
键关联的旧值将被删除掉。
这也许是你所想要的(通常是这样,这也是为什么选
择这个操作并称之为update()的原因)。
如果这不是你期望的,那么要做的也许
是抱怨(引发异常),如下:
defmergeWithoutOverlap(oneDict,otherDict):
newDict=oneDict.copy()
forkeyinotherDict.keys():
ifkeyinoneDict.keys():
raiseValueError,"thetwodictionariesaresharingkeys!
"
newDict[key]=otherDict[key]
returnnewDict
或者把二者的值结合为一个元组,例如:
defmergeWithOverlap(oneDict,otherDict):
newDict=oneDict.copy()
第九章
274
forkeyinotherDict.keys():
ifkeyinoneDict.keys():
newDict[key]=oneDict[key],otherDict[key]
else:
newDict[key]=otherDict[key]
returnnewDict
为了说明前面三个算法的不同,考虑下面两个字典:
phoneBook1={'michael':
'555-1212','mark':
'554-1121','emily':
'556-0091'}
phoneBook2={'latoya':
'555-1255','emily':
'667-1234}
如果phoneBook1可能是过时的,而phoneBook2更新一些但不够完整,那么正
确的用法可能是phoneBooke1.update(phoneBook2)。
如果认为两个电话本不
应该有重复的键时,使用newBook=mergeWithoutOverlap(phoneBook1,
phoneBook2)可以让你知道假设是否有错。
最后一种,如果一个是家里的电话本
而另一个是办公室的电话本,那么只要是后续的引用newBook['emily']的代码
能够处理newBook['emily']是元组('556-0091','667-1234')这一事实,就可
以用:
newBook=mergeWithoutOverlap(phoneBook1,phoneBook2)
拷贝:
copy模块
回到拷贝主题上来:
[:
]和.copy()技巧适用于90%的情况。
如果你正按照Python
的精神,编写可以处理任何参数类型的函数,有时需要拷贝X而不管X是什么。
这时就需要copy模块。
它提供了两个函数,copy和deepcopy。
第一个就像序列
的分片操作[:
]或是字典的copy方法。
第二个函数更微妙并且与深度嵌套结构有
关(这正是deepcopy的意思)。
例如用分片操作[:
]完整地拷贝listOne。
这个
技术产生了新的列表,如果原来的列表中的内容是不变的对象,如数字或字符串,
这个拷贝就是“真正的”拷贝。
然而假设listOne的第一项是一个字典(或任何
其他容易变化的对象),那么listOne的拷贝的第一项只是对同一个字典的新的
引用。
所以如果你修改了那个字典,显然listOne和它的拷贝都修改了。
用一个
例子可以看得更清楚些:
>>>importcopy
>>>listOne=[{"name":
"Willie","city":
"Providence,RI"},1,"tomato",3.0]
用Python完成常见的任务
275
>>>listTwo=listOne[:
]#orlistTwo=copy.copy(listOne)
>>>listThree=copy.deepcopy(listOne)
>>>listOne.append("kid")
>>>listOne[0]["city"]="SanFrancisco,CA"
>>>printlistOne,listTwo,listThree
[{'name':
'Willie','city':
'SanFrancisco,CA'},1,'tomato',3.0,'kid']
[{'name':
'Willie','city':
'SanFrancisco,CA'},1,'tomato',3.0]
[{'name':
'Willie','city':
'Providence,RI'},1,'tomato',3.0]
正如你所见,直接修改listOne仅仅修改了listOne。
对listOne的第一项的修
改影响到listTwo,但没有影响listThree。
这就是浅度拷贝([:
])和深度拷
贝的区别。
copy模块的函数知道如何拷贝可以拷贝的内置函数(注1),包括类和
实例。
排序
在第二章你知道列表有一个排序方法,有时你想要遍历整个排好序的列表而不想
影响列表的内容。
或者你也许想列出一个排好序的元组,而元组是不可变的,不
允许有排序的方法。
唯一的解决办法是拷贝一个列表,然后派序列表。
例如:
listCopy=list(myTuple)
listCopy.sort()
foriteminlistCopy:
printitem
#或者做别的任何事情
这也是处理那些没有内在顺序的数据结构的办法,例如字典。
字典非常快的一个
原因是实现时保留了改变键的顺序的权利。
这其实不是一个问题,因为你可以拷
贝字典的键然后遍历它:
keys=myDict.keys()
#返回字典的未排序的键
keys.sort()
forkeyinkeys:
#答应以键为序的健值对
printkey,myDict[key]
列表的sort方法使用的是Python的标准比较方案。
但有时你需要别的方案。
例
注1:
有些对象是不可拷贝的,如模块、文件对象和套接字。
记住,文件对象与磁盘上的文
件是不同的。
第九章
276
如当你对一个单词列表排序时,大小写也许是没有意义的。
而对字符串的标准比
较中,所有大写字母都在小写之前,所以'Baby'小于'apple'而'baby'大于
'apple'。
为了进行大小写无关排序,你需要定义一个有两个参数的函数,并且根
据第一个参数是小于,等于或大于第二个参数,分别返回-1,0,1。
所以你可以
这样写:
>>>defcaseIndependentSort(something,other):
...something,other=string.lower(something),string.lower(other)
...returncmp(something,other)
...
>>>testList=['this','is','A','sorted','List']
>>>testList.sort()
>>>printtestList
['A','List','is','sorted','this']
>>>testList.sort(caseIndependentSort)
>>>printtestList
['A','is','List','sorted','this']
我们使用内置的函数cmp来完成比较工作。
我们的排序函数只是把两项变成小写
字母然后排序。
也请注意小写转换是在比较函数局部范围内的,所以列表中的元
素并没有因排序而修改。
随机:
random模块
怎样随机排列一个序列呢?
比如一个文本行的列表。
最简单的办法是使用random
模块里的choice函数,它随机地返回序列的元素作为它的参数(注2)。
为了避
免重复地得到同样的行,记住删除已经选择了的项。
当操作一个列表对象时,使
用remove方法:
whilemyList:
#当myList空时停止循环
element=random.choice(myList)
myList.remove(element)
printelement,
如果你需要随机处理一个非列表对象,通常最简单的办法是把它转换为一个列表,
注2:
random模块提供了很多有用的函数,例如random函数,它返回一个介于0和1之间
的随机浮点数。
用Python完成常见的任务
277
然后对这个列表作随机处理,而不是对每种数据类型都采用新的办法。
这似乎是
一个浪费的办法,也许要产生一个很巨大的列表。
然而一般来说,对你似乎很大
的数据,对于计算机来说可能不那么大,感谢Python的引用系统。
而且不用对每
种数据类型采用不同的方法,所节约的时间是很多的。
Python的设计初衷就是要
节约时间;如果那意味着运行一个稍慢一点或者大一点的程序,那就让它这样吧。
如果你正在处理大量的数据,也许值得优化。
但只有当确实需要优化时才去优化,
否则将是浪费时间。
定义新的数据结构
对于数据结构来说,不要重复发明轮子这一原则尤其重要。
例如,Python的列表
和字典也许不是你习惯于使用的,但如果这些数据结构可以满足要求,你应当避
免设计自己的数据结构,它们使用的算法已经在各种情形下测试过,并且快而稳
定。
但有时这些算法的接口对某个特别的任务不方便。
例如,计算机科学的教科书中经常用其他数据结构术语如队列、堆栈来描述算法。
为了使用这些算法,定义与这些数据结构有同样方法的数据结构也许是有意义的。
(比如堆栈的pop和push,或者队列的enqueue和dequeue)。
而且,重用内置的
列表类型来实现堆栈也是有意义的。
换句话说,你需要行为像堆栈但却是基于列
表的结构。
最简单的办法是用一个类来包裹一个列表。
为了最低限度地实现一个
类,你可以这样写:
classStack:
def__init__(self,data):
self._data=list(data)
defpush(self,item):
self._data.append(item)
defpop(self):
item=self._data[-1]
delself._data[-1]
returnitem
下面的语句不仅易写,易懂,而且易读,易用。
>>>thingsToDo=Stack(['writetomom','invitefriendover','washthe
kid'])
第九章
278
>>>thingsToDo.push('dothedishes')
>>>printthingsToDo.pop()
dothedishes
>>>printthingsToDo.pop()
washthekid
在上面的堆栈类中用了两个标准的Python命名习惯,第一个是类名由大写字母开
始,以便与函数名区别开。
另一个是_data属性以一个下划线开始,这介于公共
属性(不以下划线开始)和私有属性之间(以两个下划线开始,参见第六章“类”)。
而Python的保留字在开始和结尾都有两个下划线。
这里的意思是:
_data是一个
属性,用户不应该直接访问,类的设计者希望这个“伪私有”属性只被类和子类
的方法使用。
定义新的列表和字典:
UserList和UserDict模块
前面展示的堆栈类作了恰当的工作。
它采取了关于堆栈的最小定义,只支持两个
操作:
push和pop。
然而,很快你就发现列表的特性确实好,比如可以用
for...in...的方式访问所有的成员。
这可以通过重用已有的代码来实现。
在这
里你应当应用UserList模块里定义的类UserList作为基类,堆栈由此派生而来。
库里也包括UserDict模块,它是一个封装字典的类。
一般来说,它们是用于特
别子类的基类。
#从UserList模块中导入UserList类
fromUserListimportUserList
#继承UserList类
classStack(UserList):
push=UserList.append
defpop(self):
item=self[-1]#使用__getitem__
delself[-1]
returnitem
这个堆栈是UserList的一个子类。
UserList类通过定义特别的__getitem__
和__delitem__方法实现了方括号的运算,所以前面代码里的pop能工作。
你
不必定义你自己的__init__方法,因为UserList已经定义了一个相当不错的。
最后只是说明push方法等于UserList的append方法。
现在我们可以用列表和
堆栈两种方式来操作了。
用Python完成常见的任务
279
>>>thingsToDo=Stack(['writetomom','invitefriendover','washthe
kid'])
>>>printthingsToDo#从UserList继承
['writetomom','invitefriendover','washthekid']
>>>thingsToDo.pop()
'washthekid'
>>>thingsToDo.push('changetheoil')
>>>forchoreinthingsToDo:
#我们也可以用"for..in.."遍历内容
...printchore#因为有__getitem__
...
writetomom
invitefriendover
changetheoil
注意:
当我们写这本书时,GuidovanRossom宣布在Python1.5.2(以及后续版本里),列表
对象将增加一个pop方法,它也有一个可选参数来指定pop的索引(缺省是列表最后的
一个成员)。
文件操作
脚本语言的设计目标之一是帮助人们快速而简单地做重复工作。
Web管理员,系
统管理员和程序员的经常需要做的一件事是:
从一个文件集合中选出一个子集,
对这个子集做某种操作,并把结果写到一个或一组输出文件中(例如,在某个目
录里的每个文件里,隔行查找以非#字符开头的行的最后一个词,并把它与文件
名一起打印出来)。
人们为这类任务已经设计了特定的工具,例如sed和awk。
我
们发现Python能很简单地完成这个工作。
操作一个文本文件里的每一行
当解析一个包含文本的输入文件时,sys模块是非常有用的。
在它的属性中有三
个文件对象,分别称为sys.stdin、sys.stdout和sys.stderr。
名字来源于三个
流的概念:
分别为标准输入、标准输出和标准错误。
它们与命令行工具有关,
print语句使用标准输出。
它是一个文件对象,具有以写模式打开的文件对象的
所有输出方法,如write和writelines。
另一个常用的流是标准输入(stdin),
它也是一个文件对象,不过它拥有的是输入方法,例如read、readline和
readlines。
下面的脚本会算出通过“管道”输入的文件行数:
第九章
280
importsys
data=sys.stdin.readlines()
print"Counted",len(data),"lines."
在Unix上你可以做如下的测试:
%catcountlines.py|pythoncountlines.py
Counted3lines.
在Windows或DOS上,你可以:
C:
\>typecountlines.py|pythoncountlines.py
Counted3lines.
当实现简单的过滤操作时,readlines函数是有用的。
这里有一些过滤操作的例
子:
寻找所有以#开始的行
importsys
forlineinsys.stdin.readlines():
ifline[0]=='#':
printline,
注意print语句后的逗号是需要的,因为line字符串里已经有一个换行符。
取出一个文件的第四列(这里列是由空白符定义的)
importsys,string
forlineinsys.stdin.readlines():
words=string.split(line)
iflen(words)>=4:
printwords[3]
我们通过words列表的长度判断是否确实至少有四个列,最后两行可以用
try/except惯用法代替,这在Python里是常见的:
try:
printwords[3]
exceptIndexError:
#没有足够的列
pass
取出文件的第四列,列由冒号分开,并用小写输出
importsys,string
forlineinsys.stdin.readlines():
words=string.split(line,':
')
用Python完成常见的任务
281
iflen(words)>=4:
printstring.lower(words[3])
打印头10行,最后10行,并隔行打印输出
importsys,string
lines=sys.stdin.readlines()
sys.stdout.writelines(lines[:
10])
#头10行
sys.stdout.writelines(lines[-10:
])
#最后10行
forlineIndexinrange(0,len(lines),2):
#0,2,4,......
sys.stdout.write(lines[lineIndex])
#0,2,4,......行
计算单词“Python”在一个文件里出现的次数
importstring
text=open(fname).read()
printstring.count(text,'Python')
把一个文件的列变换为一个列表的行
在这个比较复杂的例子里,任务是“转置”一个文件,设想你有这样一个文
件:
Name:
WillieMarkGuidoMaryRachelAhmed
Level:
543164
Tag#:
123444515515512418815132
而你希望它变成这样:
Name:
Level:
Tag#:
Willie51234
Mark44451
...
你可以用下面的代码:
importsys,string
lines=sys.stdin.readlines()
wordlists=[]
forlineinlines:
words=string.split(line)
wordlists.append(words)
forrowinrange(len(wordlists[0])):
forcolinrange(len(wordlists)):
printwordlists[col][row]+'\t',
print
当然你应当用更多的防卫性编程技巧来处理一些可能的情况,比如也许不是
所有的行都有相同的单词数,也许丢失了数据等等。
这些就作为练习留给读
者。
第九章
282
选择数据块的大小
前面的所有例子都假设你能一次读入整个文件。
然而有时候这是不可能的,比如
在内存较小的计算机上处理大文件,或者处理不断地增加的文件(如日志文件)。
对这种情况你可以用一个while/readline组合,一次读入文件的一小部分直到
读完。
对于不是基于行的文本文件,你必须一次读入一个字符:
#逐字地读入
while1:
next=sys.stdin.read
(1)
#读入一个单字符串
ifnotnext:
#或者读到EOF时是空串
break
处理字符串'next'
注意文件对象的read()方法在文件结尾时返回一个空串,并由此而跳出循环。
然
而更常见的是你将处理基于行的文本文件,并且一次处理一行:
#逐行地读入
while1:
next=sys.stdin.readline()
#读入一个单行字符串
ifnotnext:
#或者读到EOF时是空串
break
处理行'next'
处理命令行上指定的一组文件
能够读stdin是一个