Python:正则表达式
1. 正则表达式概述
正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串, 描述了模式的重复或者表述多个字符。
正则表达式能按照某种模式匹配一系列有相似特征的字符串。
换句话说, 它们能够匹配多个字符串。
不同语言的正则表达式有差异,本文叙述是Python的正则表达式。
解释代码大多摘自《Python编程快速上手 让繁琐工作自动化》
2. 正则表达式书写
正则表达式就是一个字符串,与普通字符串不同的是,正则表达式包含了0个或多个表达式符号以及特殊字符,详见《Python核心编程》1.2节。
# 正则表达式书写 'hing' '\wing' '123456' '\d\d\d\d\d\d' 'regex.py' '.*\.py'
3. 创建正则表达式对象
孤立的一个正则表达式并不能起到匹配字符串的作用,要让其能够匹配目标字符,需要创建一个正则表达式对象。通常向compile()函数传入一个原始字符形式的正则表达式,即 r'.....'
>>> # re模块的compile()函数将返回(创建)一个Regex模式对象 >>> import re >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
4. 常用的正则表达式模式
4.1 括号分组
>>> Regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') >>> mo = Regex.search('My number is 415-555-4242.') >>> Regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') # 创建Regex对象 >>> mo = Regex.search('My number is 415-555-4242.') # 返回Match对象 >>> mo.group() # 调用Regex对象的group()方法将返回整个匹配文本 '415-555-4242' >>> mo.group(1) '415' >>> mo.group(2) '555-4242' >>> mo.group(0) '415-555-4242' >>> mo.groups() ('415', '555-4242') >>> a,b = mo.groups() # groups()方法返回多个值得元组 >>> a '415' >>> b '555-4242' >>>
4.2 用管道匹配多个分组
>>> heroRegex = re.compile (r'Batman|Tina Fey') >>> mo1 = heroRegex.search('Batman and Tina Fey.') >>> mo1.group() 'Batman' >>> mo2 = heroRegex.search('Tina Fey and Batman.') >>> mo2.group() 'Tina Fey
4.3 用问号实现可选匹配
>>> batRegex = re.compile(r'Bat(wo)?man') # 如果'wo'没有用括号括起来,则可选的字符将是Batwo >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman'
4.4 用星号匹配零次或多次
>>> batRegex = re.compile(r'Bat(wo)*man') # 如果要匹配'*'号则用\* >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman' >>> mo3 = batRegex.search('The Adventures of Batwowowowoman') >>> mo3.group() 'Batwowowowoman
4.5 用加号匹配一次或多次
>>> batRegex = re.compile(r'Bat(wo)+man') # 如果要匹配+号用\+ >>> mo1 = batRegex.search('The Adventures of Batwoman') >>> mo1.group() 'Batwoman' >>> mo2 = batRegex.search('The Adventures of Batwowowowoman') >>> mo2.group() 'Batwowowowoman' >>> mo3 = batRegex.search('The Adventures of Batman') >>> mo3 == None True
4.6 用花括号匹配特定次数
下面代码的 “?” 表示非贪心匹配。问号在正则表达式中可能有两种含义: 声明非贪心匹配或表示可选的分组。这两种含义是完全无关的。
>>> greedyHaRegex = re.compile(r'(Ha){3,5}') # 若果要匹配{,则用\{ >>> mo1 = greedyHaRegex.search('HaHaHaHaHa') >>> mo1.group() 'HaHaHaHaHa' >>> nongreedyHaRegex = re.compile(r'(Ha){3,5}?') >>> mo2 = nongreedyHaRegex.search('HaHaHaHaHa') >>> mo2.group() 'HaHaHa'
5. 贪心和非贪心匹配
利用非贪心匹配的目的往往在于不想让通配符(.)连通配符之外的匹配字符也被匹配,代码如下。当然3.6也是非贪心匹配的一个例子
>>> nongreedyRegex = re.compile(r'<.*?>') >>> mo = nongreedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man>' >>> greedyRegex = re.compile(r'<.*>') >>> mo = greedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man> for dinner.>'
6. Regex 对象常用方法
如上所述,compile()函数创建了一个Regex对象,Regex对象常用方法如下
6.1 search(), group(), groups()
>> Regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') >>> mo = Regex.search('My number is 415-555-4242.') >>> Regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') # 创建Regex对象 >>> mo = Regex.search('My number is 415-555-4242.') # 返回Match对象 >>> mo.group() # 调用Regex对象的group()方法将返回整个匹配文本 '415-555-4242' >>> mo.group(1) '415' >>> mo.group(2) '555-4242' >>> mo.group(0) '415-555-4242' >>> mo.groups() ('415', '555-4242') >>> a,b = mo.groups() # groups()方法返回多个值得元组 >>> a '415' >>> b '555-4242' >>>
6.2 findall()
如果调用在一个没有分组的正则表达式上,findall()将返回一个匹配字符串的列表。
如果调用在一个有分组的正则表达式上,findall()将返回一个字符串的元组的列表(每个分组对应一个字符串)
>>> Regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') # has no groups >>> Regex.findall('Cell: 415-555-9999 Work: 212-555-0000') ['415-555-9999', '212-555-0000'] >>> Regex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)') # has groups >>> Regex.findall('Cell: 415-555-9999 Work: 212-555-0000') [('415', '555', '1122'), ('212', '555', '0000')]
6.3 sub()
>>> namesRegex = re.compile(r'Agent \w+') >>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.') 'CENSORED gave the secret documents to CENSORED.' >>> namesRegex = re.compile(r'Agent \w+') >>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.' , 1) # 匹配1次 'CENSORED gave the secret documents to Agent Bob.'
7. re.IGNOREC ASE、 re.DOTALL 和 re.VERBOSE
要让正则表达式不区分大小写,可以向 re.compile()传入 re.IGNORECASE 或 re.I,作为第二个参数。
通过传入 re.DOTALL 作为 re.compile()的第二个参数, 可以让句点字符匹配所有字符, 包括换行字符。
要在多行正则表达式中添加注释,则向 re.compile()传入变量 re.VERBOSE, 作为第二个参数。
>>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL | re.VERBOSE)
8. (?:…)
>>> re.findall(r'http://(?:\w+\.)*(\w+\.com)', 'http://google.com http://www.google.com http://code.google.com') ['google.com', 'google.com', 'google.com'] >>>
9.代码实践
# (文件读写)疯狂填词2.py ''' 创建一个疯狂填词( Mad Libs)程序,它将读入文本文件, 并让用户在该文本文件中出现 ADJECTIVE、 NOUN、 ADVERB 或 VERB 等单词的地方, 加上他们自己的文本。例如,一个文本文件可能看起来像这样: The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events. 程序将找到这些出现的单词, 并提示用户取代它们。 Enter an adjective: silly Enter a noun: chandelier Enter a verb: screamed Enter a noun: pickup truck 以下的文本文件将被创建: The silly panda walked to the chandelier and then screamed. A nearby pickup truck was unaffected by these events. 结果应该打印到屏幕上, 并保存为一个新的文本文件。 ''' import re def mad_libs(filename_path, save_path): with open(filename_path,'r') as strings: # 相对路径下的文档 words = strings.read() Regex = re.compile(r'\w[A-Z]+') # \w :匹配1个任何字母、数字或下划线 finds = Regex.findall(words) for i in finds: replace = input('输入你想替换 {} 的单词:\n'.format(i)) Regex2 = re.compile(i) words = Regex2.sub(replace,words,1) # 这个变量必须要是words与上面一致否则只打印最后替换的一个,可以画栈堆图跟踪这个变量的值 print(words) # strings.close() 不用这一行,with 上下文管理器会自动关闭 with open(save_path,'a') as txt: txt.write(words + '\n') #分行写 txt.close() # save_txt = open('保存疯狂填词文档.txt','a') # save_txt.write(words) # save_txt.close() if __name__ == '__main__': filename_path = input('输入要替换的txt文本路径:') # '疯狂填词原始文档.txt' save_path = input('输入要保存的文件路径(包含文件名称):') # '保存疯狂填词文档.txt' mad_libs(filename_path, save_path)