摘录 python核心编程
python的re模块允许多线程共享一个已编译的正则表达式对象,也支持命名子组。下表是常见的正则表达式属性:
函数/方法 | 描述 |
仅仅是re模块函数 | |
compile(pattern,flags=0) | 使用任何可选的标记来编译正则表达式的模式 ,然后返回一个正则表达式对象 |
re模块函数和正则表达式对象的方法 | |
match(pattern,string,flags=0) | 尝试使用带有可选标记的正则表达式的模式来匹配字符串,成功则返回匹配的对象,失败则返回None |
search(pattern,string,flags=0) | 使用可选标记搜索字符串中第一次出现的正则表达式模式,成功则返回匹配对象,失败则返回None |
findall(pattern,string[,flags]) | 查找字符串中所有(非重复)出现的正则表达式模式,返回一个匹配列表 |
finditer(pattern,string,[,flags]) | 和findall()函数相同,但返回的是一个迭代器。对于每次匹配,迭代器都返回一个匹配对象 |
split(pattern,string,max=0) | 根据正则表达式的模式分隔符,split函数将字符串分割为列表,然后返回成功匹配的列表,分割最多操作max次(默认分割所有匹配成功的位置) |
re模块函数和正则表达式对象方法 | |
sub(pattern,repl,string,count=0) | 使用repl替换正则表达式模式在字符串中出现的位置,除非定义count,否则替换所有 |
purge() | 清除隐式编译的正则表达式模式 |
常用的匹配对象方法 | |
group(num=0) | 返回整个匹配对象,或者编号为num的特定子组 |
groups(default=None) | 返回一个包含所有匹配子组的元组(如果没有,则返回一个空元组) |
groupdict(default=None) | 返回一个包含所有匹配的命名子组的字典,所有子组名称作为字典的键(如没有,则返回一个空字典) |
常用的模块属性 | |
re.I、re.IGNORECASE | 不区分大写的匹配 |
re.L、re.LOCALE | 根据所使用的本地语言环通过\w\W\b\B\s\S实现匹配 |
re.M、re.MULTILINE | ^和$分别匹配目标字符串中行的起始和结尾,而不是严格的匹配整个字符串本身的开始和结尾 |
re.S、re.DOTALL | 点号.通常匹配除了换行符\n之外的所有单个字符,该标记表示点号能够匹配全部字符 |
re.X、re.VERBOSE | 通过反斜线转义,否则所有空格加上#(以及在该行中所有后续问题)都被忽略,除非在一个字符类中或者允许注释并且提高可读性 |
compile()编译正则表达式
在模式匹配发生之前,正则表达式模式必须编译成正则表达式对象,而且正则表达式在执行的过程中可能进行多次的比较操作。所以,强烈建议使用compile函数进行预编译,用以提升程序的执行性能。其实所有的模块函数会对已编译的对象进行缓存。
匹配对象及其group()和groups()方法
处理正则表达式的时候,除了正则表达式对象外,还有一个叫做匹配对象的类型。成功调用match()和search()返回的对象就是匹配对象。
匹配对象有两个主要的方法:group()和groups()函数。二者的区别在于:前者可以返回整个匹配对象或者特定子组,后者仅返回包含全部子组的元组。
match()方法实现匹配字符串
match()方法视图从字符串的起始位置部分对模式进行匹配,成功则返回一个匹配对象,失败返回None,而匹配对象的group()方法能够显示成功的匹配:
>>> import re >>> m = re.match('foo','foo') >>> m <_sre.SRE_Match object; span=(0, 3), match='foo'> >>> if m is not None: ... m.group() ... 'foo'
>>> n = re.match('hello,foo!','foo') >>> if n is not None:n.group() ... >>> n
第二个例子中,由于foo并不是在开始的位置,所有没有成功。
search()在一个字符串中查找模式
search()的工作方式和match()完全一致,只是search()会用他的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。成功则返回匹配对象,否则返回None:
>>> n = re.match('foo','hello,foo!') >>> if n is not None:ngroup() ... >>> >>> n = re.search('foo','hello,foo!') >>> if n is not None:n.group() ... 'foo'
匹配多个字符
>>> bt = 'bat|bet|bit' >>> m = re.match(bt,'bat') >>> if m is not None:m.group() ... 'bat' >>> m = re.match(bt,'blt') >>> if m is not None:m.group() ... >>> m = re.match(bt,'he bit me') >>> if m is not None:m.group() ... >>> m = re.search(bt,'he bit me') >>> if m is not None:m.group() ... 'bit'
匹配任何单个字符
先看一个点号不能匹配换行符的示例:
>>> end = '.end' >>> m = re.match(end,'bend') >>> if m is not None:m.group() ... 'bend' >>> m = re.match(end,'\nbend') >>> if m is not None:m.group() ... >>> m = re.search(end,'The end') >>> if m is not None:m.group() ... ' end
再看一下搜索一个真正的句点的示例:
>>> patt314 = '3.14' >>> pi_ptt = '3\.14' >>> m = re.match(pi_ptt,'3.14') >>> if m is not None:m.group() ... '3.14' >>> m = re.match(patt314,'3014') >>> if m is not None:m.group() ... '3014' >>> m = re.search(patt314,'3.14') >>> if m is not None:m.group() ... '3.14'
>>> m = re.match(pi_ptt,'3014')
>>> if m is not None:m.group()
...
上述例子又让我们强化认识了'3.14'和'3\.14'的区别。
创建字符集
下面的示例,用于展示[cr][23][dp][o2]和r2d2|c3po之间的区别:(会发现r2d2|c3po的限制将比[cr][23][dp][o2]更为严格)
>>> m = re.match('[cr][23][dp][o2]','c3po') >>> if m is not None:m.group() ... 'c3po' >>> m = re.match('[cr][23][dp][o2]','c2do') >>> if m is not None:m.group() ... 'c2do' >>> m = re.match('r2d2|c3po','c2do') >>> if m is not None:m.group() ... >>> m = re.match('r2d2|c3po','r2d2') >>> if m is not None:m.group() ... 'r2d2'
重复、特殊字符以及分组
正则表达式最常见的情况包括:特殊字符的使用、正则表达式模式的重复出现、使用小括号对匹配模式的各部分进行分组和提取
我们可以使用正则表达式'\w+@\w+\.com'表示一个简单的邮件地址。当我们需要再域名前加上主机名,例如www.xxx.com,那就必须修改现有的正则表达式。同时,为了表示主机名是可选的,需要创建一个模式来匹配主机名,使用?操作符来表示该模式出现0或者一次: \w+@(\w+\.)?\w+\.com
>>> patt = '\w+@(\w+\.)?\w+\.com' >>> re.match(patt,'somebody@xxx.com').group() 'somebody@xxx.com' >>> re.match(patt,'somebody@www.xxx.com').group() 'somebody@www.xxx.com'
>>> re.match(patt,'somebody@xxx.yyy.zzz.com').group()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
针对上述例子的最后一句,下面进行扩展,允许任意数量的中间子域名存在:
>>> patt = '\w+@(\w+\.)*\w+\.com' >>> re.match(patt,'somebody@xxx.yyy.zzz.com').group() 'somebody@xxx.yyy.zzz.com'
一般的,采用使用小括号来匹配和保存子组,以便于后续处理,而不是确定一个正则表达式匹配之后在一个单独的子程序里面手动编码来解析字符串:
>>> m = re.match('\w\w\w-\d\d\d','abc-123') >>> if m is not None:m.group() ... 'abc-123' >>> m = re.match('\w\w\w-\d\d\d','abc-xyz') >>> if m is not None:m.group() ...
扩展,修改上述正则表达式,使该正则表达式能够提取字母数字字符串和数字:
>>> m = re.match('(\w\w\w)-(\d\d\d)','abc-123') >>> m.group() 'abc-123' >>> m.group(1) 'abc' >>> m.group(2) '123' >>> m.groups() ('abc', '123')
下面的例子展示了不同的分组排列,以及group()和groups()函数的执行情况:
>>> m = re.match('ab','ab') >>> m.group() 'ab' >>> m.groups() () >>> m = re.match('(ab)','ab') >>> m.group() 'ab' >>> m.group(1) 'ab' >>> m.group(2) Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: no such group >>> m.groups() ('ab',) >>> m = re.match('(a)(b)','ab') >>> m.group() 'ab' >>> m.group(1) 'a' >>> m.group(2) 'b' >>> m.groups() ('a', 'b') >>> m = re.match('(a(b))','ab') >>> m.group() 'ab' >>> m.group(1) 'ab' >>> m.group(2) 'b' >>> m.groups() ('ab', 'b')
匹配字符串的起始和结尾以及单词边界
下面例子展示了表示位置的正则表达式操作符。更多的用于表示搜索而不是匹配,因为match()总是从字符串开始位置进行匹配:
>>> import re >>> m = re.search('^The','The end') >>> if m is not None: ... m.group() ... 'The' >>> m = re.search('^The','end The') >>> if m is not None: ... m.group() ... >>> m = re.search(r'\bthe','bite the dog') >>> if m is not None: ... m.group() ... 'the' >>> m = re.search(r'the','bite the dog') >>> if m is not None: ... m.group() ... 'the' >>> m = re.search(r'the','bitethe dog') >>> if m is not None: ... m.group() ... 'the' >>> m = re.search(r'\Bthe','bite the dog') >>> if m is not None: ... m.group() ...
使用findall()和finditer()查找每一次出现的位置
findall()查询字符串中某个正则表达式模式全部的非重复出现情况。于search()执行的搜索字符串类似,但是不同之处在于:findall()总是返回一个列表,列表按顺序包含所有成功匹配的部分:
>>> re.findall('car','carry the barcardi to the car') ['car', 'car', 'car']
finditer()于findall类似,但是返回的是一个迭代器,使得更节省内存。(对于迭代器,可以使用迭代器相关的知识提取内容)
使用sub()和subn()搜索和替换
两者的作用基本一致,都是将某个字符串中所有匹配正则表达式的部分进行某种形式的替换,相比于sub(),subn()还返回一个表示替换的总数,替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回:
>>> re.subn('X','Mr.Smith','attn:X\n\nDear X,\n') ('attn:Mr.Smith\n\nDear Mr.Smith,\n', 2) >>> print(re.sub('X','Mr.Smith','attn:X\n\nDear X,\n')) attn:Mr.Smith Dear Mr.Smith,
在限定模式上使用split()分割字符串
先来一个普通的字符串分割:
>>> re.split(':','str1:str2:str3') ['str1', 'str2', 'str3']
再来见识一下巨大威力的分割效果:例如用于web站点的简单解析器,这是普通的字符串分割没有的强大的处理方式
>>> DATA = ( ... 'Moutain view, CA 94040', ... 'Sunny, CA', ... 'Los Altos, 94023', ... 'Cuper 95014', ... 'Palo Alto CA', ... ) >>> for i in DATA: ... print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ',i)) ... ['Moutain view', 'CA', '94040'] ['Sunny', 'CA'] ['Los Altos', '94023'] ['Cuper', '95014'] ['Palo Alto', 'CA']
上述例子中的正则表达式使用了一个组件:使用split语句基于逗号分割字符串,或者如果空格紧跟在五个数字或者两个大写字母之后,就用split语句分割该空格。而这种分割效果很难用str.split()达到。
这里涉及到的正则表达式的扩展符号的使用,足够让我们另写一篇文章介绍了。