用Python做文本处理 第一章Word下载.docx
《用Python做文本处理 第一章Word下载.docx》由会员分享,可在线阅读,更多相关《用Python做文本处理 第一章Word下载.docx(49页珍藏版)》请在冰点文库上搜索。
BUILTIN--list
列表对象的新式类
▪1.1.3.11
BUILTIN--tuple
元组对象的新式类
▪1.1.3.12
模块--UserString
包装字符串对象的类
▪1.1.3.13
BUILTIN--str
字符串对象的新式类
▪1.1.3.13.1
格式化字符串
▪1.1.3.13.2
在大文件里行操作
本章讨论Python处理文本的能力。
Python语法和语义可以参考附录A或GuidovanRossum的_PythonTutorial_在http:
//python.org/doc/current/tut/tut.html。
这里的重点不是Python语言介绍,但也没有特别关注文本处理。
在1.1,我会关注一些Python语言的编程技术,并不适合Python初学者。
编程技术的讨论着重于适用文字处理的情况--其他编程任务本书不会详细讨论。
在1.2,我会介绍Python的标准库在文字处理程序里的应用。
有些Python标准库模块是远远不适合文字处理的,在这方面你可能不会使用它们。
这种边缘的模块我只会非常简单的作一两行说明。
更详细的资料请翻看Python的文档。
这第一个主题富有挑战性。
如果是首次接触高阶函数对于不熟悉这方面的Python程序员来说有点困难。
不要过于害怕这个困难--您可以了解到其他书里没有的东西。
如果函数式编程概念你不熟悉,我建议你先看附录A,特别是其中最后一节的介绍。
在文字处理中,一个经常的动作就是处理一系列句子,这些句子有一定的相似性。
多数情况下,这些句子是用换行分隔,但有时也会用其他的形式。
此外,Python从文件里按行读取在不同平台可能会有所差异。
显然,这样的分块还没有完全统一,会包含不同的数据。
但在这里我们先不考虑这个,我们假设每个分块句都包含了正确有用的资料。
举例而言,我们想选择那些符合我们要求的行文字,其标准是'
isCond()'
#*----------命令行式风格的行选择-----------------------#
selected=[]#收集可用的行
fp=open(filename):
forlineinfp.readlines():
#Python2.2->
"
forlineinfp:
"
ifisCond(line):
#(2.2版本这里是惰性读取)
selected.append(line)
delline#清除不用的名称
这样读取几行没有任何-错误-(效率问题参看[xreadlines])。
但阅读这样的程序会多花几秒钟。
在我看来,即使是这么一小段程序也不符合-简单-的设计思想,即使它的运作确实是这样的。
'
line'
这个变量略显多余(不光要考虑循环以后它的保留价值,同时循环的每一步都要给它赋值)。
在函数式风格里,我们可以写的很简单:
#*----------函数式风格的行选择-------------------------#
selected=filter(isCond,open(filename).readlines())
#Py2.2->
filter(isCond,open(filename))
在具体应用里,一个经常做的操作就是处理一个有很多行的日志文件。
各式各样的应用程序都会产生日志文件,最常见的是那些导致系统变化的应用程序需要纪录每一次操作。
例如,Python的Windows安装程序会产生一个'
INSTALL.LOG'
,其中包含了安装中各个步骤的清单。
以下是从我的电脑复制的一个这个文件:
#------------INSTALL.LOG样本--------------------------#
Title:
Python2.2
Source:
C:
\DOWNLOAD\PYTHON-2.2.EXE|02-23-2002|01:
40:
54|7074248
MadeDir:
D:
\Python22
FileCopy:
\Python22\UNWISE.EXE|05-24-2001|12:
59:
30||...
RegDBKey:
Software\Microsoft\Windows\CurrentVersion\Uninstall\Py...
RegDBVal:
\Python22\w9xpopen.exe|12-21-2001|12:
22:
34||...
\PYTHON22\DLLs
FileOverwrite:
\WINDOWS\SYSTEM\MSVCRT.DLL||||295000|770c8856
RegDBRoot:
2
Software\Microsoft\Windows\CurrentVersion\AppPaths\Py...
\PYTHON22\Python.exe
ShellLink:
\WINDOWS\StartMenu\Programs\Python2.2\UninstallPy...
LinkInfo:
\Python22\UNWISE.EXE|D:
\PYTHON22||0|1|0|
\WINDOWS\StartMenu\Programs\Python2.2\Python...
\Python22\python.exe|D:
\PYTHON22|D:
\PYTHON22\...
你可以看到,每一个行动记录都是那几种类型。
如果要处理每种不同类型的行动纪录(每种类型会有不同的数据字段结构),最简单的是用布尔函数来确定每一行的类型,例如:
#*-------每一行的布尔判定函数---------------------------#
defisFileCopy(line):
returnline[:
10]=='
FileCopy:
#或者使用line.startswith(...)
defisFileOverwrite(line):
15]=='
FileOverwrite:
字符串函数`"
.startswith()`不容易出错。
用一种稍微紧凑的函数式编程风格,您可以写成这样:
#*-----------函数式风格判定----------------------------#
isRegDBRoot=lambdaline:
line[:
11]=='
RegDBRoot:
isRegDBKey=lambdaline:
RegDBKey:
isRegDBVal=lambdaline:
RegDBVal:
选择某种类型的行可以这样做:
#*-----------Selectlinesthatfillpredicate----------#
lines=open(r'
d:
\python22\install.log'
).readlines()
regroot_lines=filter(isRegDBRoot,lines)
但是,如果你要采用多种标准来选择,那函数式风格会成为累赘。
例如假设你对所有"
RegDB"
行有兴趣,你可以写一个新的函数过滤器:
#*---------------FindtheRegDBlines------------------#
defisAnyRegDB(line):
ifline[:
return1
elifline[:
else:
return0
#最好使用line.startswith(...)
每个条件都写一个函数会产生大量的函数。
更重要的是,每个函数都需要去写,而且还增加了bug产生的机会。
用组合的方式,您可以用几个过滤器组合成一种新的过滤条件。
例如:
#*-------------用2种条件来过滤------------------------#
shortline=lambdaline:
len(line)<
25
short_regvals=filter(shortline,filter(isRegDBVal,lines))
在这个例子中,我们依靠先前定义的过滤器函数。
在'
shortline()'
或'
isRegDBVal()'
产生的任何错误都会影响这个过滤,'
isShortRegVal()'
第三方过滤器函数则不会对这个产生影响。
使用嵌套的filter会比较难以阅读--尤其是数量超过两个以上。
对同一个字符串做若干处理使用`map()`
也是同样会嵌套多层。
例如,假设您希望把一行由空格分割的小写文字翻转组合起来并改为大写,创建这样的函数很简单,他们可以嵌套在`map()`里:
#*------------一行文字同时多种转换---------------------#
defflip(s):
returns[:
-1]
normalize=lambdas:
'
.join(s.split())
cap_flip_norms=map(str.upper,map(flip,map(normalize,lines)))
这种类型的`map()`
或`filter()`
会难以阅读,应当避免。
此外,有时会交替使用`map()`
和`filter()`,使问题更加严重。
例如,假设您要对每一行做一些处理,同时还要对这些行做一些判断。
为了避免这种问题,许多程序员会改回那种更详细的命令行式编码风格,例如包装几个循环再使用一些保存中间结果的临时变量。
在函数式编程风格里是可以避免这种过多嵌套的。
关键是要有几个聪明的高阶组合函数。
一般而言,高阶函数会需要函数作为参数并且返回的结果也是一个函数对象。
一阶函数会需要一些数据作为参数并产生一种数据结构作为返回值(典型的结构会像一个list或dict)。
与此相反,更高阶的函数的"
inputs"
和"
outputs"
会是某种函数对象--通常在最终某处才开始真正流程处理运作。
高阶函数的一个典型例子就是一个-函数工厂-:
这个函数(或类)会返回一个函数或是几个函数的包裹器,这些返回值都会包含某种"
配置"
。
做一个"
HelloWorld"
例子,一个简单的函数工厂可以是一个"
adder"
工厂。
一个加法器工厂只是为了表明可以做什么,它并没有什么真正的用途。
几乎每一个解释函数工厂都会使用这样的一个例子,如:
>
>
defadder_factory(n):
...returnlambdam,n=n:
m+n
...
add10=adder_factory(10)
add10
<
function<
lambda>
at0x00FB0020>
add10(4)
14
add10(20)
30
add5=adder_factory(5)
add5(4)
9
对于文字处理来说,简单的函数工厂都不如高阶组合函数。
高阶组合函数需要几个(通常是一阶)函数作为参数并返回一个新的函数,这个函数会把几个具体的函数组合起来。
下面是一个简单的高阶组合函数表,只需要令人惊讶的几行:
#-------------------combinatorial.py-------------------#
fromoperatorimportmul,add,truth
apply_each=lambdafns,args=[]:
map(apply,fns,[args]*len(fns))
bools=lambdalst:
map(truth,lst)
bool_each=lambdafns,args=[]:
bools(apply_each(fns,args))
conjoin=lambdafns,args=[]:
reduce(mul,bool_each(fns,args))
all=lambdafns:
lambdaarg,fns=fns:
conjoin(fns,(arg,))
both=lambdaf,g:
all((f,g))
all3=lambdaf,g,h:
all((f,g,h))
and_=lambdaf,g:
lambdax,f=f,g=g:
f(x)andg(x)
disjoin=lambdafns,args=[]:
reduce(add,bool_each(fns,args))
some=lambdafns:
disjoin(fns,(arg,))
either=lambdaf,g:
some((f,g))
anyof3=lambdaf,g,h:
some((f,g,h))
compose=lambdaf,g:
f(g(x))
compose3=lambdaf,g,h:
lambdax,f=f,g=g,h=h:
f(g(h(x)))
ident=lambdax:
x
即使只有十几行,这些组合函数就足以提供方便了。
让我们看看我们如何使用这些高阶函数来简化一些较早的例子。
和上面采用一样的函数命名:
#-----一些高阶函数的使用例子-------------------------#
#Don'
tnestfilters,justproducefuncthatdoesboth
#不需要嵌套filter,只需要用both来组合条件
short_regvals=filter(both(shortline,isRegVal),lines)
tmultiplyadhocfunctions,justdescribeneed
#不需要增加函数,只需要描述需求
regroot_lines=\
filter(some([isRegDBRoot,isRegDBKey,isRegDBVal]),lines)
tnesttransformations,makeonecombinedtransform
#不需要嵌套转换,只需要建立一个组合
capFlipNorm=compose3(upper,flip,normalize)
cap_flip_norms=map(capFlipNorm,lines)
在例子中,我们使用组合函数'
capFlipNorm'
非常有可读性。
相应的`map()`
行只是--单一的表示--执行在所有行上做这个处理。
这个例子已经说明了组合函数的灵活性。
通过浓缩几个操作就能代替以前好几个`map()`嵌套,我们还可以把这个组合过的操作运用到程序其他地方。
作为一个经验,我建议不要在任何一个代码行上使用一个以上的`filter()`和`map()`。
如果这些"
list"
应用函数需要嵌套,应该使用一个保存中间结果的名称变量来增加可读性。
连续的函数式编程风格读起来会比较像命令行式风格--不过奇妙的是Python允许无缝组合不同的编程风格。
#*------限制map()/filter()的嵌套层数---------------#
intermed=filter(niceProperty,map(someTransform,lines))
final=map(otherTransform,intermed)
任何连续嵌套的`filter()`
或`map()`程序都可以用高阶组合函数转换成单一的函数。
而这些产生的步骤也只需要很少几步。
在总代码数量上抵消了定义组合函数的消耗。
总之,函数式风格的代码通常只有命令行式风格大约一半的长度(越少的代码一般意味着有相对较少的bug)。
组合函数还有一个好处是可以提供一个完整的布尔代数应用,而不需要使用明显的调用(在这个意义上'
combinatorial.py'
使用`operator.add`
和`operator.mul`不是偶然的)。
例如,一个简单的运算就可以表示复杂的逻辑关系,例如:
#*----------使用值运算的简单布尔表达式----------------#
satisfied=(thisorthat)and(fooorbar)
在文字处理上,这些判断函数产生的真值往往是一段需要处理的文字,例如:
#*----------函数返回值的逻辑运算----------------------#
satisfied=(thisP(s)orthatP(s))and(fooP(s)orbarP(s))
在上面这个表达式中,一些判断函数对相同的字串(或其他东西)进行判断,有逻辑关系的结果就会被计算。
这表达式本身也说明了这种逻辑关系。
清楚的命名--特别是如果你想在参数相同的前提上多次运算的话--将可以方便的创建一个有实际功能的表达式:
#*------组合函数的逻辑运算-------------------------#
satisfiedP=both(either(thisP,thatP),either(fooP,barP))
利用组合技术创建的判断函数同样可以应用在其他函数里:
#*------使用组合的布尔函数---------------------------#
selected=filter(satisfiedP,lines)
模块'
提供了一些最常见的高阶组合函数。
但是,还有加强的余地。
创建一个个人高阶函数库来充实您当前的文本处理库。
题目:
1.'
里定义的一些函数严格地说不是组合函数。
在精确的意义上说,一个组合函数应该用一个或几个函数作为参数并返回一个或多个组合后的函数对象。
哪些函数不是“严格”的组合函数,并确定这些函数的返回值到底是什么样的东西。
2.函数'
both()'
和'
and_()'
看上去很相似。
但是他们有所不同,'
比较像Python里的'
and'
,可以提前中断运算。
f=lambdan:
n**2>
10
g=lambdan:
100/n>
and_(f,g)(5)
1
both(f,g)(5)
and_(f,g)(0)
0
both(f,g)(0)
Traceback(mostrecentcalllast):
这种形式可以用第一个函数的结果来提前终止运算。
如果第一个函数返回一个假值,那第二个函数就不会计算。
∙a.建立一个相似的'
or_()'
∙b.建立同样可以提前终止运算的'
shortcut_all()'
shortcut_some()'
∙c.阐述那些情况最好使用不提前中断的组合函数而不使用会提前中断的函数
3.解释'
ident()'
的真正作用
提示:
假设你有几行文字,其中一些行可能是空字符串。
怎么设计过滤器可以找到所有开始为'
#'
的行?
4.'
not_()'
可能应该是一个组合库的扩展功能。
我们可以这样定义:
not_=lambdaf:
lambdax,f=f:
notf(x)
探索一下'
函数对组合程序有哪些帮助。
5.'
apply_each()'
被用在建立其他组合函数。
不过它也有一些实际用途。
apply_each(map(adder_factory,range(5)),(10,))
[10,11,12,13,14]
如何简化文本的多次处理。
6.不像'
all()'
some()'
,'
compose()'
compose3()'
只能使用固定数目的函数做参数。
建造一个可以使用任何数目的函数为参数的合成函数。
7.还有什么适合文本处理的高阶组合函数?
考虑一下常用的一阶函数。
Python有丰富的标准数据类型--附录A讨论各种内置的类型。
与此同时,Python编程的一个重要原则让其他语言的程序员觉得类型不是很重要。
根据Python的“普遍多态性原则”(我自己创造的名词),对象能“作什么”比“是什么”重要。
另一种对这个原则的解释是:
如果它像一个鸭子而且叫声也像鸭子,那就把它当作是一只鸭子。
一般而言,多态的设计思想是让同样的函数可以在不同的类型上工作。
在C++或Java,例如,您可以使用特化方式来重载某些操作以适用于多种数据类型(根据需求有所不同)。
#------------C++标示型多态性---------------------------#
#include<
stdio.h>
classPrint{
public:
voidprint(inti){printf("
int
%d\n"
i);
}
voidprint(doubled){printf("
double
%f\n"
d);
voidprint(floatf){printf("
float
f);
};
main(