用Python作文本处理 第二章 -- 基本的字符串运用_第1页
用Python作文本处理 第二章 -- 基本的字符串运用_第2页
用Python作文本处理 第二章 -- 基本的字符串运用_第3页
用Python作文本处理 第二章 -- 基本的字符串运用_第4页
用Python作文本处理 第二章 -- 基本的字符串运用_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、用Python作文本处理/第二章目录 1第一节 - 常用的操作 2主题 - 快速排序 3主题 - 排版 4主题 - 处理字段 5主题 - 字词数统计 6主题 - 以二进制数据传送ASCII码信息 7主题 - 词频统计第一节 - 常用的操作主题 - 快速排序排序是文字处理中大多数任务的关键所在。幸运的是,在Python里,使用.sort的效率还不错。此外,在列表的任何不同对象都可以排序而不需要像C语言那样需要统一的元素(对于混合复数和Unicode字符串的列表排序在最近的几个Python版本里会触发TypeError异常)。参考:complex+列表排序的顺序有一种自然顺序,特别是不同类型混合的

2、排序顺序都是Python的默认顺序。很多时候,你需要特定的顺序。特别是对于文本里的行做排序往往需要的不是简单的字母顺序。通常一行里有用的信息起始位置并不是第一个字符:人名里的姓往往是第二个单词;服务器日志里IP地址可能固定在某个字段;金额合计可能在每一行的第70列等等。只使用默认排序这些内容只会毫无意义。列表排序.sort()支持自定义比较函数参数。这个比较函数的功能是返回-1则表示前者排在后者之前,返回0则表示二者顺序相同,返回1则表示后者排在前者之前。内置函数cmp()就是.sort()的默认比较函数(在速度上lst.sort()远远超过lst.sort(cmp))。对于不太长的列表使用自

3、定义比较函数可以快速的解决问题。在很多情况下,甚至可以直接使用一个lambda表达式来完成任务。说到速度,使用自定义比较函数效率会很低。部分原因是Python的函数调用开销,函数本身也会增加花费的时间。不过有一种技术“Schwartzian转换”可以加速这种自定义排序。Schwartzian转换是兰德尔施瓦兹在Perl中最先开始使用的,但其中的技巧同样适用于Python。使用Schwartzian转换主要包括三个步骤,(准确的来说这是Guttman-Rosler转换(GRT),同样基于Schwartzian转换): 1. 将列表转换为可以用默认排序的列表。 2. 使用.sort()排序。 3.

4、 转回原先的格式。这项技术的主要作用是花费仅仅O(2N)转换就可以使用默认的O(N log N)排序。如果任务里排序时间是主要因素的话,使用这项技术将大大提高效率(唯一的限制就是转换花费的时间不会很多)。下面是一个简单的例子。排序比较方式是比较每一行的第四个单词。有的行单词数少于4个。测试文件约20,000行(1兆左右)使用Schwartzian转换排序花费不到2秒,而使用自定义比较函数则花费12秒以上(排序结果一样)。确切时间不会很准确,但很明显效率提高了6倍。 #- schwartzian_sort.py -# #- 测试按第四个单词排序的速度 #- 如果两行都有4个以上单词,则按照第4个

5、第5个。来排序 #- 没有4个单词的行排在有4个单词的行后面 #- 没有4个单词的行之间按照默认顺序排列 import sys, string, time wrerr = sys.stderr.write #- 自定义比较函数 def fourth_word(ln1,ln2): lst1 = string.split(ln1) lst2 = string.split(ln2) #- 比较4个单词以上的行 if len(lst1) = 4 and len(lst2) = 4: return cmp(lst13:,lst23:) #- 少于4个单词的行排在后面 elif len(lst1) = 4

6、 and len(lst2) 4: return -1 #- 少于4个单词的行排在后面 elif len(lst1) = 4: return 1 else: # 默认顺序 return cmp(ln1,ln2) #- 不计算读取时间 lines = open(sys.argv1).readlines() #- 计时使用自定义比较函数排序 start = time.time() lines.sort(fourth_word) end = time.time() wrerr(Custom comparison func in%3.2f secsn% (end-start) # open(tmp.c

7、ustom,w).writelines(lines) #- 不计算读取时间 lines = open(sys.argv1).readlines() #- 计时Schwartzian转换排序 start = time.time() for n in range(len(lines): # 开始转换 lst = string.split(linesn) if len(lst) = 4: # 把排序内容放在前面 linesn = (lst3:, linesn) else: # 少于4个单词的行排在后面 linesn = (377, linesn) lines.sort() # 排序 for n in

8、 range(len(lines): # 转换回原先内容 linesn = linesn1 end = time.time() wrerr(Schwartzian transform sort in%3.2f secsn% (end-start) # open(tmp.schwartzian,w).writelines(lines)这只有一个特别的例子,但读者应该能够用任何形式来使用这种技术,特别是对于大文件。主题 - 排版虽然使用ASCII文本作为通讯格式并不好-通常不会很复杂文件不会很大-但其生命力还是很强的。README文件,HOWTO文件,电子邮件,新闻组,包括本书都仍然是使用ASCI

9、I码文本(至少原文加工技术通常是很有价值的)。此外,许多像HTML和Latex的格式往往也需要手动修改,清晰的排版是非常重要的。段落排版对于文本文件来说是极为常见的工作。Python2.3增加了textwrap模块做一些有限的排版工作。在大多数情况下,这项工作可以使用文本编辑器来完成。不过,有时候自动化排版会更方便。这项工作很简单,比较奇怪的是,Python没有相应的标准模块功能实现这一点。有一个formatter.DumbWriter类和formatter.AbstractWriter抽象类可以用于此项工作。相关讨论在第5章,坦率地说,使用这些类需要大量的定制工作而且很复杂,往往不适合用于解

10、决手头的任务。下面是一种简单的解决办法,可以当作命令行工具(从标准输入读取和输出到标准输出),或用于较大的应用程序。 #- reformat_para.py -# # 简单排版。主要用于左右对齐。 LEFT,RIGHT,CENTER = LEFT,RIGHT,CENTER def reformat_para(para=,left=0,right=72,just=LEFT): words = para.split() lines = line = word = 0 end_words = 0 while not end_words: if len(wordsword) right-left: #

11、 过长的单词 line = wordsword word +=1 if word = len(words): end_words = 1 else: # 收集一行可以容纳的单词 while len(line)+len(wordsword) = len(words): end_words = 1 break lines.append(line) line = if just=CENTER: r, l = right, left return n.join( *left+ln.center(r-l) for ln in lines) elif just=RIGHT: return n.join(l

12、ine.rjust(right) for line in lines) else: # left justify return n.join( *left+line for line in lines) if _name_=_main_: import sys if len(sys.argv) 4: print Please specify left_margin, right_marg, justification else: left = int(sys.argv1) right = int(sys.argv2) just = sys.argv3.upper() # 排版每一段 for p

13、 in sys.stdin.read().split(nn): print reformat_para(p,left,right,just),n留给读者一些改进任务。例如您可能需要首行缩进。或者有些段落需要的格式不适合用此排版(例如题头等等)。具体的应用程序还可能需要确定如何分段等等。主题 - 处理字段数据表,DBMS,日志文件以及平面数据库往往在每行放置同样的纪录,每条记录有相同的字段。通常这些字段要么是用分割符间隔要么是用固定位置来存放。分析这些记录的结构很容易,进行表格计算上也同样很简单。对于各种文本结构数据,可以使用几乎相同的代码来做处理。下面的例子中提供了一种通用的框架来处理结构化文

14、本。 #- fields_stats.py -# # 处理文本数据库里的多个字段 import operator from types import * from xreadlines import xreadlines # 需要Python2.1,提高效率 # 2.1以下使用.readline() #- 格式常量 DELIMITED = 1 FLATFILE = 2 #- 一些简单的处理过程(使用函数式风格) nillFunc = lambda lst: None toFloat = lambda lst: map(float, lst) avg_lst = lambda lst: redu

15、ce(operator.add, toFloat(lst)/len(lst) sum_lst = lambda lst: reduce(operator.add, toFloat(lst) max_lst = lambda lst: reduce(max, toFloat(lst) class FieldStats: 统计资料 text_db 可以是字符串(包括Unicode字符串)或文件类对象 style 有2种格式(DELIMITED, FLATFILE)分隔符或位置 默认使用分隔符格式 column_positions 位置列表,第一列的位置为1。 例如:(1,7,40) 表示3个字段,

16、起始位置分别为1,7,40 field_funcs 是字典,储存需要处理的字段和对应处理过程。 例如:1:avg_lst, 4:sum_lst, 5:max_lst 表示对第一个字段做求平均值处理 对第四个字段做合计处理,对第5个字段求最大值 其他字段不做处理。 def _init_(self, text_db=, style=DELIMITED, delimiter=, column_positions=(1,), field_funcs= ): self.text_db = text_db self.style = style self.delimiter = delimiter self

17、.column_positions = column_positions self.field_funcs = field_funcs def calc(self): 计算 #- 第一步先建立列表的列表来存放数据。 used_cols = self.field_funcs.keys() used_cols.sort() #: 不使用column0 columns = for n in range(1+used_cols-1): # 提示: 这里可以使用*num来代替 columns.append() #- 第二步生成需要计算的列表数据 # text_db是字符串对象 if type(self.

18、text_db) in (StringType,UnicodeType): for line in self.text_db.split(n): fields = self.splitter(line) for col in used_cols: field = fieldscol-1 # 注意这里是由0开始的索引 columnscol.append(field) else: # text_db是文件对象 for line in xreadlines(self.text_db): fields = self.splitter(line) for col in used_cols: field

19、= fieldscol-1 # 注意这里是由0开始的索引 columnscol.append(field) #- 第三步作处理计算 results = None * (1+used_cols-1) for col in used_cols: resultscol = apply(self.field_funcscol,(columnscol,) #- Finally, return the result list return results def splitter(self, line): 分解一行为字段表 if self.style = DELIMITED: return line.sp

20、lit(self.delimiter) elif self.style = FLATFILE: fields = # 注意这里是以0开始的索引 # 最后加上结束位置 num_positions = len(self.column_positions) offsets = (pos-1) for pos in self.column_positions offsets.append(len(line) for pos in range(num_positions): start = offsetspos end = offsetspos+1 fields.append(linestart:end

21、) return fields else: raise ValueError, Text database must be DELIMITED or FLATFILE #- 测试数据 # First Name, Last Name, Salary, Years Seniority, Department delim = Kevin,Smith,50000,5,Media Relations Tom,Woo,30000,7,Accounting Sally,Jones,62000,10,Management .strip() # no leading/trailing newlines # Co

22、mment First Last Salary Years Dept flat = tech note Kevin Smith 50000 5 Media Relations more filler Tom Woo 30000 7 Accounting yet more. Sally Jones 62000 10 Management .strip() # no leading/trailing newlines #- Run self-test code if _name_ = _main_: getdelim = FieldStats(delim, field_funcs=3:avg_ls

23、t,4:max_lst) print Delimited Calculations: results = getdelim.calc() print Average salary -, results3 print Max years worked -, results4 getflat = FieldStats(flat, field_funcs=3:avg_lst,4:max_lst, style=FLATFILE, column_positions=(15,25,35,45,52) print Flat Calculations: results = getflat.calc() pri

24、nt Average salary -, results3 print Max years worked -, results4上面的例子中包括了一些效率上的考虑,可以用于大型数据集。首先,FieldStats类是动态处理文本,而没有保存整个数据。xreadlines.xreadlines()是一个极其快速和有效的行阅读器,但它需要Python2.1+,其他版本可以使用FILE.readline()或FILE.readlines()。此外,只关心实际上需要用到的数据,以便节省内存。这里没有考虑同时使用多个相关字段做处理的应用,不过可以提取出数据作再次运算。还有就是没有考虑在同一个字段上作多种处

25、理,这个留给读者作思考。主题 - 字词数统计Unix系统下有个工具wc可以做这个任务。这是个基本工具不能统计段落。wc只能统计字符,单词和行。有几个命令选项可以控制显示的结果,不过我很少用到它们。在写这一段的时候,我发现自己的系统里没有装wc。下面的例子实际上是一个增强版wc,不过缺少了很多命令行选项。在Python里可以非常简单的实现wc的功能。其主要使用的技术是.join()和.split()。 #- wc.py -# # 统计字符数,单词数,行数和段落数 # 可以STDIN输入或使用文件通配符 import sys, glob if len(sys.argv) 1: c, w, l, p

26、 = 0, 0, 0, 0 for pat in sys.argv1: for file in glob.glob(pat): s = open(file).read() wc = len(s), len(s.split(), len(s.split(n), len(s.split(nn) print t.join(map(str, wc),t+file c, w, l, p = c+wc0, w+wc1, l+wc2, p+wc3 wc = (c,w,l,p) print t.join(map(str, wc), tTOTAL else: s = sys.stdin.read() wc =

27、len(s), len(s.split(), len(s.split(n), len(s.split(nn) print t.join(map(str, wc), tSTDIN这个小功能可以在一个函数里实现,不过它太过于紧凑。不过在使用解释器的环境下者只需要两行。上面的解决方法符合Python的准则,用一种显而易见的方式来完成任务。还有个方法也可以完成同样的任务,读者可以思考一下(这只是好玩): wc = map(len,s+map(s.split,(None,n,nn)如果再加上print语句,就可以用一行来解决问题了。主题 - 以二进制数据传送ASCII码信息许多文本信息都是采用7位ASC

28、II编码。特别是用在互联网传送的时候非ASCII码的信息需要先编成7位ASCII码才能正常传送,否则会因为最高位被分解导致出现乱码。像简单邮件传输协议(SMTP),网络新闻传输协议(NNTP)或HTTP的内容编码等等都会需要用到编解码。编码8位二进制数据成ASCII码有一些通用的技术。编码技术大都是将二进制数据转换成十六进制数据。UU编码是一种很早的编码标准,主要用于新闻组和BBS传输二进制文件。Binhex编码主要用于Mac的系统。base64是MIME的一种编码方式。这些编码技术基本上是在四个字节的ASCII码和三个字节的二进制数据之间转换,开始标记和结束标记等略有不同。QP编码也是一种M

29、IME编码,不过它的编码长度是可变的,主要用于非ASCII码信息,对7位ASCII文本无需再次编码,仅将8位数据转换为7位。Python提供了上述几种编码的相关模块。包装好的模块uu,binhex,base64和quopri操作起来和文件对象差不多。当然之间也略有不同。例如binhex是在编码以后才关闭输出文件,这使得它无法用于像cStringIO之类的文件对象。所有的这些编码工具都要使用到底层的C模块binascii。实际上真正执行转换的是binascii模块,不过它不会区分正确的编码块大小。标准库并没有提供通用的编解码功能,不过很容易就可以实现一个通用的编解码程序: #- encode_b

30、inary.py -# # Provide encoders for arbitrary binary data # in Python strings. Handles block size issues # transparently, and returns a string. # Precompression of the input string can reduce # or eliminate any size penalty for encoding. import sys import zlib import binascii UU = 45 BASE64 = 57 BINH

31、EX = sys.maxint def ASCIIencode(s=, type=BASE64, compress=1): ASCII文本转换为二进制数据 # 先选择编码方式 if type = BASE64: encode = binascii.b2a_base64 elif type = UU: encode = binascii.b2a_uu elif type = BINHEX: encode = binascii.b2a_hqx else: raise ValueError, Encoding must be in UU, BASE64, BINHEX # 再判断是否进行压缩 if

32、compress: s = press(s) # 一块一块开始编码 offset = 0 blocks = while 1: blocks.append(encode(soffset:offset+type) offset += type if offset len(s): break # 返回编码后的数据 return .join(blocks) def ASCIIdecode(s=, type=BASE64, compress=1): 由二进制数据解码至ASCII文本 # 解码 if type = BASE64: s = binascii.a2b_base64(s) eli

33、f type = BINHEX: s = binascii.a2b_hqx(s) elif type = UU: s = .join(binascii.a2b_uu(line) for line in s.split(n) # 判断是否进行解压缩 if compress: s = zlib.decompress(s) # 返回结果 return s # Encode/decode STDIN for self-test if _name_ = _main_: decode, TYPE = 0, BASE64 for arg in sys.argv: if arg.lower()=-d: dec

34、ode = 1 elif arg.upper()=UU: TYPE=UU elif arg.upper()=BINHEX: TYPE=BINHEX elif arg.upper()=BASE64: TYPE=BASE64 if decode: print ASCIIdecode(sys.stdin.read(),type=TYPE) else: print ASCIIencode(sys.stdin.read(),type=TYPE)上面的例子并没有包含任何头数据或需要的分界数据,这些可以通过使用uu,mimify或MimeWriter来解决。或者你也可以编写自己需要的encode_binar

35、y.py.主题 - 词频统计分析文本里单词的出现频率是很有用的。在文字处理里,几乎要么是处理字节要么是处理单词。创建词频统计很简单,只需要使用Python字典就可以了,不过不是每个人都能立即找到最明显的解决方案。下面的这个例子有很好的通用性,提供了多种实用功能,并且还可用于命令行操作模式。 #- histogram.py -# # 统计词频 # 有几个工具函数可以提取需要的结果 from string import split, maketrans, translate, punctuation, digits import sys from types import * import typ

36、es def word_histogram(source): 对词作统计(不包括标点和数字) hist = trans = maketrans(,) if type(source) in (StringType,UnicodeType): # 字符串 for word in split(source): word = translate(word, trans, punctuation+digits) if len(word) 0: histword = hist.get(word,0) + 1 elif hasattr(source,read): # 文件 try: from xreadli

37、nes import xreadlines # 测试是否可用 for line in xreadlines(source): for word in split(line): word = translate(word, trans, punctuation+digits) if len(word) 0: histword = hist.get(word,0) + 1 except ImportError: # 使用旧版本 line = source.readline() # 速度慢,但内存占用少 while line: for word in split(line): word = tran

38、slate(word, trans, punctuation+digits) if len(word) 0: histword = hist.get(word,0) + 1 line = source.readline() else: raise TypeError, source must be a string-like or file-like object return hist def char_histogram(source, sizehint=1024*1024): hist = if type(source) in (StringType,UnicodeType): # 字符

39、串 for char in source: histchar = hist.get(char,0) + 1 elif hasattr(source,read): # 文件 chunk = source.read(sizehint) while chunk: for char in chunk: histchar = hist.get(char,0) + 1 chunk = source.read(sizehint) else: raise TypeError, source must be a string-like or file-like object return hist def mo

40、st_common(hist, num=1): pairs = for pair in hist.items(): pairs.append(pair1,pair0) pairs.sort() pairs.reverse() return pairs:num def first_things(hist, num=1): pairs = things = hist.keys() things.sort() for thing in things: pairs.append(thing,histthing) pairs.sort() return pairs:num if _name_ = _ma

41、in_: if len(sys.argv) 1: hist = word_histogram(open(sys.argv1) else: hist = word_histogram(sys.stdin) print Ten most common words: for pair in most_common(hist, 10): print t, pair1, pair0 print First ten words alphabetically: for pair in first_things(hist, 10): print t, pair0, pair1 # a more practical command-line version might use: # for pair in most_common(hist,len(hist): # print pair1,t,pair0几个设计的选择有些武断。词频统计里去除了标点符号,只保留字母,同时还区分大小写,这可能不是理想的选择。first_things()和most_common()只返回最初的几项排序结果。取自/

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论