Python 正则表达式 re 模块

  • 原创
  • Madman
  • /
  • 2018-06-14 15:06
  • /
  • 0
  • 445 次阅读

Synopsis: 如果你想匹配的是字面字符串,那么你通常只需要调用基本字符串方法就行, 比如 str.find() , str.endswith() , str.startswith() 或者类似的方法。对于复杂的匹配需要使用正则表达式和 re 模块,如果你想使用同一个模式去做多次匹配,你应该先将模式字符串预编译为模式对象。match() 总是从字符串开始去匹配,如果你想查找字符串任意部分的模式出现位置, 使用 findall() 方法去代替

强烈推荐正则表达式在线测试网站: https://regex101.com/

1. 标准库模块 re

更多详情参考官方文档:

Python3中使用re模块支持正则表达式(Regular Expression),需要定义一个用于匹配的模式(pattern)字符串,以及一个要匹配的字符串(string)。简单的匹配:

In [1]: import re

In [2]: m = re.match('My', 'My name is wangy')

In [3]: m
Out[3]: <_sre.SRE_Match object; span=(0, 2), match='My'>

In [4]: m.group()  # 等价于m.group(0)
Out[4]: 'My'

In [5]: m.start(), m.end()
Out[5]: (0, 2)

In [6]: m.span()
Out[6]: (0, 2)

其中,My是正则表达式模式,最简单的,只匹配字符My本身。而My name is wangy是想要检查的字符串,re.match()函数用于查看字符串是不是以正则模式开头

如果你仅仅是做一次简单的文本匹配/搜索操作的话,可以直接使用 re 模块级别的函数,比如re.match。如果你打算做大量的匹配和搜索操作的话,最好先编译正则表达式,然后再重复使用它:

In [1]: import re

In [2]: p = re.compile('[a-z]+')  # [a-z]+ 是正则模式,表示1个或多个小写字母

In [3]: p
Out[3]: re.compile(r'[a-z]+', re.UNICODE)

In [4]: if p.match('hello123'):   # p是预编译后的正则模式,它也有match等方法,只是参数不同,不需要再传入正则模式。判断字符串'hello123'是否以1个或多个小写字母开头
   ...:     print('yes')
   ...: else:
   ...:     print('no')
   ...:     
yes

In [5]: if p.match('123hi'):      # 重用预编译过的正则模式
   ...:     print('yes')
   ...: else:
   ...:     print('no')
   ...:     
no

模块级别的函数会将最近编译过的模式缓存起来,因此并不会消耗太多的性能, 但是如果使用预编译模式的话,你将会减少查找和一些额外的处理损耗。

1.1 使用match()从字符串开头开始匹配

可以使用模块级别的re.match()或预编译模式的p.match(),如果字符串是以正则表达式开头,则表明匹配成功,返回匹配到的对象,比如<_sre.SRE_Match object; span=(0, 2), match='My'>,如果匹配失败,返回None

In [1]: import re

In [2]: m1 = re.match('wangy', 'wangy is a handsome boy.')  # 模块级的match方法

In [3]: m1  # 匹配成功,返回Match对象
Out[3]: <_sre.SRE_Match object; span=(0, 5), match='wangy'>

In [4]: m1.group()  # Match对象有group()、start()、end()、span()等方法
Out[4]: 'wangy'

In [5]: m2 = re.match('mayun', 'wangy is a handsome boy.')  # 匹配失败

In [6]: type(m2)  # 返回None
Out[6]: NoneType

In [7]: p = re.compile('wangy')  # 预编译正则模式也是可以的

In [8]: p.match('wangy is a handsome boy.')  # 调用预编译正则模式的match方法
Out[8]: <_sre.SRE_Match object; span=(0, 5), match='wangy'>

如果字符串中有多个地方与正则表达式匹配的话,search()方法返回第一次匹配到的结果:

search(pattern, string, flags=0)
    Scan through string looking for a match to the pattern, returning
    a match object, or None if no match was found.
(END)
In [1]: import re

In [2]: s = 'I wish I may, I wish I might have a dish of fish tonight.'

In [3]: re.search('wish', s)
Out[3]: <_sre.SRE_Match object; span=(2, 6), match='wish'>

In [4]: re.search('wish', s).span()
Out[4]: (2, 6)

1.3 使用findall()finditer()寻找所有匹配

前面两个函数都是查找到一个匹配后就停止,如果要查找字符串中所有的匹配项,可以使用findall()

In [1]: import re

In [2]: text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'

In [3]: p = re.compile('\d+/\d+/\d+')

In [4]: p.findall(text)
Out[4]: ['11/27/2012', '3/13/2013']

findall()方法会搜索文本并以列表形式返回所有的匹配。 如果你想以迭代方式返回匹配,可以使用finditer()方法来代替,比如:

In [5]: iters = p.finditer(text)

In [6]: iters
Out[6]: <callable_iterator at 0x7f94c1703f98>

In [7]: for m in iters:
   ...:     print(m)
   ...:     
<_sre.SRE_Match object; span=(9, 19), match='11/27/2012'>
<_sre.SRE_Match object; span=(34, 43), match='3/13/2013'>

1.4 使用split()按匹配切分

字符串的str.split()方法只适应于非常简单的字符串分割情形, 它并不允许有多个分隔符或者是分隔符周围不确定的空格。 当你需要更加灵活的切割字符串的时候,最好使用 re.split() 方法:

In [1]: import re

In [2]: line = 'asdf fjdk;   afed,  fjek,asdf,   foo'

In [3]: re.split(r'[;,\s]\s*', line)  # 正则模式表示 ;或,或空白字符且它们的后面再跟0个或多个空白字符
Out[3]: ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

1.5 使用sub()替换匹配

对于简单的字面模式,直接使用字符串的str.replace()方法即可,比如:

In [1]: text = 'yeah, but no, but yeah, but no, but yeah'

In [2]: text.replace('yeah', 'yep')
Out[2]: 'yep, but no, but yep, but no, but yep'

对于复杂的模式,请使用re模块中的sub(),比如你想将形式为 11/27/2012 的日期字符串改成 2012-11-27 。示例如下:

In [1]: import re

In [2]: text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'

In [3]: re.sub('(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
Out[3]: 'Today is 2012-11-27. PyCon starts 2013-3-13.'

sub()函数中的第一个参数是被匹配的模式,第二个参数是替换模式。反斜杠数字比如\3指向前面模式的第3个捕获组,此时要加r指定为原始字符串,否则会被Python自动转义为\x03

对于更加复杂的替换,可以传递一个替换回调函数来代替。一个替换回调函数的参数是一个Match对象,也就是match()或者find()返回的对象。使用group()方法来提取特定的匹配部分。回调函数最后返回替换字符串。比如:

In [1]: import re

In [2]: from calendar import month_abbr

In [3]: def change_date(m):
   ...:     mon_name = month_abbr[int(m.group(1))]
   ...:     return '{} {} {}'.format(m.group(2), mon_name, m.group(3))
   ...: 
   ...: 

In [4]: text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'

In [5]: p = re.compile(r'(\d+)/(\d+)/(\d+)')

In [6]: p.sub(change_date, text)
Out[6]: 'Today is 27 Nov 2012. PyCon starts 13 Mar 2013.'

如果除了替换后的结果外,你还想知道有多少替换发生了,可以使用re.subn()来代替。比如:

In [7]: newtext, n = p.subn(r'\3-\1-\2', text)

In [8]: newtext
Out[8]: 'Today is 2012-11-27. PyCon starts 2013-3-13.'

In [9]: n
Out[9]: 2

2. 正则表达式语法

2.1 基本模式

语法 说明 模式示例 匹配
普通字符 普通的文本值代表自身,用于匹配非特殊字符 ab ab
. 匹配除换行符\n以外的任意一个字符。如果要匹配多行文本,可以指定re.DOTALL标志位,或者(?:.|\n)*表示匹配.\n,且作为一个非捕获组,再指定*表示0个或多个前面的非捕获组 ab. 匹配abc或abC,不匹配ab,因为b后面一定要有一个字符
\ 转义字符,比如要匹配点号.本身,需要转义它\.,如果不转义,.将有上一行所示的特殊含义 ab\. 匹配ab.com,不匹配abc
[] 匹配中括号内的一个字符
1. 中括号内的字符可以全部列出,如[abc]表示匹配字符abc
2. 也可以使用-表示范围,如[a-z]表示匹配所以小写字母中的任意一个字符
3. 本文后续要介绍的如*+等特殊字符在中括号内将失去特殊含义,如[*+()]表示匹配字符*+()
4. 本文后续要介绍的特殊字符集如\d\w等也可以放入此中括号内,继续保持特殊含义
5. 如果中括号内的字符序列前面有一个^,表示不匹配中括号内的任何一个字符,如[^0-9]表示不匹配数字,a[^0-9]c不匹配a1c,但会匹配abc
6. 要匹配字符],可以转义它,或者把它放在中括号内的首位,如a[()[\]{}]ca[]()[{}]c都可以匹配到a]c
a[0-9]b a1b或a2b

2.2 特殊字符集

语法 说明 模式示例 匹配
\d 匹配任意一个数字字符,等价于[0-9] a\db a1b
\D 匹配任意一个非数字字符,等价于[^0-9] a\Db aAb
\s 匹配任意一个空白字符,等价于[ \t\n\r\f\v] a\sb a b
\S 匹配任意一个非空白字符,等价于[^ \t\n\r\f\v] a\Sb aAb
\w 匹配任意一个 alphanumeric character,等价于[a-zA-Z0-9_] a\wb azb或aZb或a1b或a_b
\W 匹配任意一个 non-alphanumeric character,等价于[^a-zA-Z0-9_] a\Wb a-b或a b

这些字符集也可以放入[]中,比如a[\d\s]b表示匹配字符a和字符b,且中间有一个数字字符或空白字符,所以它会匹配a1ba b

Python的string模块中预先定义了一些可供我们测试用的字符串常量。我们将使用其中 的printable字符串,它包含 100 个可打印的 ASCII 字符,包括大小写字母、数字、空格 符以及标点符号:

In [1]: import string

In [2]: printable = string.printable

In [3]: len(printable)
Out[3]: 100

In [4]: printable[:50]
Out[4]: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN'

In [5]: printable[50:]
Out[5]: 'OPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

printable 中哪些字符是数字?

In [7]: re.findall('\d', printable)
Out[7]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

哪些字符是数字、字符或下划线?

In [8]: print(re.findall('\w', printable), end='')
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', '
x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_']

哪些属于空格符?

In [10]: re.findall('\s', printable)
Out[10]: [' ', '\t', '\n', '\r', '\x0b', '\x0c']

\d\w不仅会匹配ASCII字符,还可以匹配Unicode字符:

In [11]: s = 'abc' + '-/*' + '\u00ea' + '\u0115'

In [12]: re.findall('\w', s)
Out[12]: ['a', 'b', 'c', 'ê', 'ĕ']

2.3 数量

说明: 表格中 斜体prev 表示 1. 单个字符如b 2. 或者复杂的表达式如(abc)(.|\n)(这是分组的功能,见2.5)

语法 说明 模式示例 匹配
prev * 匹配0个或多个 prev尽可能多地匹配,贪婪模式,等价于{0,} ab* a或ab或abb或abbb,注意是匹配字符a后面跟0个或多个字符b
prev *? 匹配0个或多个 prev尽可能少地匹配,非贪婪模式 ab*? a,非贪婪模式下匹配0个字符b
prev + 匹配1个或多个 prev尽可能多地匹配,贪婪模式,等价于{1,} ab+ ab或abb或abbb
prev +? 匹配1个或多个 prev尽可能少地匹配,非贪婪模式 ab+? ab,非贪婪模式下匹配1个字符b
prev ? 匹配0个或1个 prev尽可能多地匹配,贪婪模式,等价于{0,1} ab? a或ab
prev ?? 匹配0个或1个 prev尽可能少地匹配,非贪婪模式 ab?? a,非贪婪模式下匹配0个字符b
prev {m} 匹配m个连续的 prev a{3} aaa
prev {m,n} 匹配m到n个连续的 prev尽可能多地匹配,贪婪模式。n可选,如果不指定,则表示m到无穷多个连续的 prev a{3,5} aaa或aaaa或aaaaa
prev {m,n}? 匹配m到n个连续的 prev尽可能少地匹配,非贪婪模式 a{3,5}? aaa

可以在*+?的后面再添加一个?,此时表示非贪婪模式匹配,Python中的正则表达式默认是贪婪模式匹配,它会在满足整个表达式要求的前提下,尽可能多地去匹配字符,具体效果见后面的示例

2.4 边界

语法 说明 模式示例 匹配
^ prev 匹配以 prev 开头的字符串(脱字符)。多行文本中,默认^只会匹配第一行的开头位置,如果设置了re.MULTILINE标志位,则^也会匹配换行符之后的开头位置 ^ab abcd
prev $ 匹配以 prev 结尾的字符串。多行文本中,默认$只会匹配最后一行的结尾位置,如果设置了re.MULTILINE标志位,则$也会匹配换行符之前的结尾位置 ab$ 只匹配ab。如果是.*ab$则会匹配123ab,否则使用ab\Z
\b 单词边界。Matches the empty string, but only at the beginning or end of a word. A word is defined as a sequence of word characters. Note that formally, \b is defined as the boundary between a \w and a \W character (or vice versa), or between \w and the beginning/end of the string. 注意: \b在Python中默认会被转义为\x08表示退格,需要将整个正则表达式指定为原始字符串(在前面加个r),即r'\bfoo\b' r'\bfoo\b'
请使用re.findall()测试
匹配foofoo.(foo)bar foo baz,但不匹配foobarfoo3
\B 非单词边界。Matches the empty string, but only when it is not at the beginning or end of a word. \B is just the opposite of \b. py\B 匹配pythonpy3py2,但不匹配pypy.py!
\A Matches only at the start of the string. \Aab abcde
\Z Matches only at the end of the string. ab\Z 123ab

2.5 分组

语法 说明 模式示例 匹配
(expr) 将小括号内的表达式作为一个分组,后面可以接表示数量的特殊字符。每个分组的编号从1开始递增,后续可以使用\1\2这样引用分组匹配到的内容 a(bc)?d ad或abcd,不匹配abcbcd
expr1 | expr2 匹配 expr1expr2a|b|c等价于[abc],都表示匹配字符abc。从左至右,如果匹配了某个表达式,则跳过后续表达式。expr1 | expr2表示作用于整个待匹配的字符串,而(expr1 | expr2)加入小括号内表示分组,不是整个字符串 p(i|u)g pig或pug
\1 引用编号为1的分组匹配到的字符串,同理\2表示引用第2个分组。注意: 此时为了不让Python自动将\1转义为\x01,需要将整个正则表达式指定为原始字符串(在前面加个r),即r'a(\d)b\1c' r'a(\d)b\1c' a3b3c
(?P<NAME>expr) 类似于(expr),同时给分组指定了一个别名NAME,注意是大写的字母P r'a(?P<quote>\d)b\1c' a3b3c
(?P=NAME) 引用别名为NAME的分组,当然也可以继续使用编号的形式引用分组如\1,此时要加r指定为原始字符串 a(?P<quote>\d)b(?P=quote)cr'a(?P<quote>\d)b\1c' a3b3c

引用命名的分组:

Named groups can be referenced in three contexts. If the pattern is (?P<quote>['"]).*?(?P=quote),matching a string quoted with either single or double quotes:

Context of reference to group "quote" Ways to reference it
in the same pattern itself 1. (?P=quote)
2. \1
when processing match object m 1. m.group('quote')
2. m.start('quote')
3. m.span('quote') 等方法
in a string passed to the repl argument of re.sub() 1. \g<quote>
2. \g<1>
3. \1

当使用match()search()时,所有的匹配会以m.group()的形式返回到对象m中。如果你用括号将某一模式包裹起来, 括号中模式匹配得到的结果归入自己的分组group(无名称)中,而调用 m.groups() 可以得到包含这些匹配的元组,如下所示:

In [1]: import re

In [2]: s = 'I wish I may, I wish I might have a dish of fish tonight.'

In [3]: m = re.search(r'(. dish\b).*(\bfish)', s)

In [4]: m.group()
Out[4]: 'a dish of fish'

In [5]: m.groups()
Out[5]: ('a dish', 'fish')

In [6]: m.group(0)
Out[6]: 'a dish of fish'

In [7]: m.group(1)
Out[7]: 'a dish'

In [8]: m.group(2)
Out[8]: 'fish'

给分组指定别名:

In [9]: m2 = re.search(r'(?P<DISH>. dish\b).*(?P<FISH>\bfish)', s)

In [10]: m2.groups()
Out[10]: ('a dish', 'fish')

In [11]: m2.group()
Out[11]: 'a dish of fish'

In [12]: m2.group('DISH')
Out[12]: 'a dish'

In [13]: m2.group('FISH')
Out[13]: 'fish'

In [14]: m2.group(1)
Out[14]: 'a dish'

In [15]: m2.group(2)
Out[15]: 'fish'

2.6 扩展语法

Python3中的正则表达式,以(?开头的都是一些扩展语法,比如上面学过的(?P<NAME>expr)(?P=NAME),还有一些常用的:

(?:expr) 非捕获组。如果分组后续要被\1这样的形式引用,使用(expr),这叫捕获组,后面可以接表示数量的特殊字符。如果不想分组被引用,就使用(?:expr),这叫非捕获组,后面可以接表示数量的特殊字符 a(?:\d)+bc,此时不支持引用分组,像r'a(?:\d)+b\1c'这样的属于语法错误 a333bc
prev(?=next) 如果后面为 next,则返回 prev ab(?=\d) 如果ab后面紧跟一个数字,则匹配,比如ab3,返回ab
prev(?!next) 如果后面不是 next,则返回 prev ab(?!\d) 如果ab后面不是紧跟一个数字,则匹配,比如abc,返回ab
(?<=prev)next 如果前面为 prev,则返回 next (?<=\d)ab 如果ab前面是一个数字,则匹配,比如1ab,返回ab
(?<!\d)ab 如果前面不是 prev,则返回 next (?<!\d)ab 如果ab前面不是一个数字,则匹配,比如Aab,返回ab
(?# comment) #井号后面的是注释内容,整个括号中的内容将被忽略
(?(g_id/g_name)yes-expr|no-expr) 如果分组编号g_id或分组别名g_name存在,则尝试匹配yes-expr,否则尝试匹配no-expr。其中,no-expr是可选的。 (\d)?a(?(1)\d|b) 当字符a前面的分组存在时,匹配1a2,不匹配1ab。当分组不存在时,匹配ab,不匹配a1或ac
(?aiLmsux) 1. 如果不想在re.compile()中指定正则标志位时,可以使用(?aiLmsux)这种形式
2. 只能用于整个正则表达式的开头
3. (?后面可以跟a/i/L/m/s/u/x中的一个或多个,它们分别对应一种标志位: re.A (ASCII-only matching), re.I (ignore case), re.L (locale dependent), re.M (multi-line), re.S (dot matches all), re.U (Unicode matching), and re.X (verbose)
(?s)/\*(.*?)\*/ 可以匹配C语言的多行注释:/* this is a multiline comment */

非捕获分组:

比如要使用多个分割符或者是分隔符周围不确定的空格时,来分割一个字符串:

In [1]: import re

In [2]: line = 'asdf fjdk; afed, fjek,asdf, foo'

In [3]: re.split(r'[;,\s]\s*', line)
Out[3]: ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

这里使用了[],如果你想在正则表达式中用括号()包含一个捕获分组,那么被匹配的文本(那些分隔符)也将出现在结果列表中:

In [4]: re.split(r'(;|,|\s)\s*', line)
Out[4]: ['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']

如果你不想保留分割字符串到结果列表中去,但仍然需要使用到括号来分组正则表达式的话, 确保你的分组是非捕获分组,形如(?:...) 。比如:

In [5]: re.split(r'(?:,|;|\s)\s*', line)
Out[5]: ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

3. 实例

3.1 字符串忽略大小写的搜索替换

为了在文本操作时忽略大小写,你需要在使用re模块的时候给这些操作提供re.IGNORECASE标志参数。比如:

In [1]: import re

In [2]: text = 'UPPER PYTHON, lower python, Mixed Python'

In [3]: re.findall('python', text)
Out[3]: ['python']

In [4]: re.findall('python', text, flags=re.IGNORECASE)
Out[4]: ['PYTHON', 'python', 'Python']

In [5]: re.sub('python', 'snake', text, flags=re.IGNORECASE)
Out[5]: 'UPPER snake, lower snake, Mixed snake'

这样不管原字符串中的是大写、小写或首字母大写的python,全替换为小写的snake,如果你想替换字符串能自动跟被匹配字符串的大小写保持一致,需要提供一个辅助函数:

In [1]: import re

In [2]: text = 'UPPER PYTHON, lower python, Mixed Python'

In [3]: def matchcase(word):
   ...:     def replace(m):
   ...:         text = m.group()
   ...:         if text.isupper():
   ...:             return word.upper()
   ...:         elif text.islower():
   ...:             return word.lower()
   ...:         elif text[0].isupper():
   ...:             return word.capitalize()
   ...:         else:
   ...:             return word
   ...:     return replace
   ...: 
   ...: 

In [4]: re.sub('python', matchcase('snake'), text, flags=re.IGNORECASE)
Out[4]: 'UPPER SNAKE, lower snake, Mixed Snake'

matchcase('snake')返回了一个回调函数(闭包,参考:http://www.madmalls.com/blog/post/closure-and-decorator-in-python/#1 ),参数必须是Match对象

Python支持的其它正则表达式的标志位flags:

  • re.A: 或re.ASCII,使\w/\W/d/\D/\s/\S/\b/\B只匹配ASCII字符,不匹配Unicode字符。等同于(?a)
  • re.I: 或re.IGNORECASE,忽略大小写,[A-Z]会匹配小写字母。等同于(?i)
  • re.L: 或re.LOCALE,Make\w/\W/\b/\Band case-insensitive matching dependent on the current locale. This flag can be used only with bytes patterns。等同于(?L)
  • re.M: 或re.MULTILINE,多行模式,改变^$的默认行为,详情见上面的 2.4 边界。等同于(?m)
  • re.S: 或re.DOTALL,Make the.special character match any character at all, including a newline; without this flag, .will match anything except a newline。等同于(?s)
  • re.U: 或re.UNICODE,默认使用此标志位,\w/\W/d/\D/\s/\S/\b/\B会匹配Unicode字符,如果指定了re.A标志,则re.U失效。等同于(?u)
  • re.X: 或re.VERBOSE,允许整个正则表达式写成多行,忽略空白字符,并可以添加#开头的注释,这样更美观。等同于(?x)
In [1]: import re

In [2]: a = re.compile(r"""\d +  # the integral part
   ...:                    \.    # the decimal point
   ...:                    \d *  # some fractional digits""", re.X)

In [3]: b = re.compile(r"\d+\.\d*")

In [4]: a.match('10.2')
Out[4]: <_sre.SRE_Match object; span=(0, 4), match='10.2'>

In [5]: b.match('10.2')
Out[5]: <_sre.SRE_Match object; span=(0, 4), match='10.2'>

可以一次指定多个标志位:

In [1]: import re

In [2]: text = '''UPPER PYTHON
   ...: lower python
   ...: Mixed Python'''

In [3]: re.sub('python$', 'snake', text)  # 三个python都没被替换,$表示只能匹配最后一个python,但是大小写不匹配,它是Python,所以没有被替换
Out[3]: 'UPPER PYTHON\nlower python\nMixed Python'

In [4]: re.sub('python$', 'snake', text, flags=re.IGNORECASE)  # 指定了re.I,忽略大小写,最一个Python被替换成了snake
Out[4]: 'UPPER PYTHON\nlower python\nMixed snake'

In [5]: re.sub('python$', 'snake', text, flags=re.IGNORECASE|re.MULTILINE)  # 同时指定re.I和re.M,三个都被替换
Out[5]: 'UPPER snake\nlower snake\nMixed snake'
In [6]: p = re.compile(r'python$', re.I|re.M)

In [7]: p  # 预编译后的正则模式除了默认的re.UNICODE标志位外,又添加了re.I和re.M两个标志位
Out[7]: re.compile(r'python$', re.IGNORECASE|re.MULTILINE|re.UNICODE)

In [8]: p.sub('snake', text)
Out[8]: 'UPPER snake\nlower snake\nMixed snake'

In [9]: re.sub('(?im)python$', 'snake', text)  # re.I|re.M 等价于 (?im),注意(?im)必须位于整个正则的开头
Out[9]: 'UPPER snake\nlower snake\nMixed snake'

3.2 最短匹配模式: 非贪婪

你正在试着用正则表达式匹配某个文本模式,但是它找到的是模式的最长可能匹配:

In [1]: import re

In [2]: p = re.compile(r'"(.*)"')

In [3]: text1 = 'Computer says "no."'

In [4]: p.findall(text1)
Out[4]: ['no.']

In [5]: text2 = 'Computer says "no." Phone says "yes."'

In [6]: p.findall(text2)  # 默认是贪婪模式,(.*)会尽可能多的匹配,只要后面有一个"就能满足整个正则,所以匹配到最后一个冒号之前了
Out[6]: ['no." Phone says "yes.']

为了修正这个问题,可以在模式中的*操作符后面加上?修饰符:

In [7]: p2 = re.compile(r'"(.*?)"')

In [8]: p2.findall(text2)
Out[8]: ['no.', 'yes.']

3.3 多行匹配

你正在试着使用正则表达式去匹配一大块的文本,而你需要跨越多行去匹配,当你用点.去匹配任意字符的时候,忘记了点.不能匹配换行符\n的事实。比如,假设你想试着去匹配C语言分割的注释:

In [1]: import re

In [2]: text1 = '/* this is a comment */'

In [3]: text2 = '''/* this is a
   ...: multiline comment */
   ...: '''

In [4]: p = re.compile(r'/\*(.*?)\*/')

In [5]: p.findall(text1)  # 能正确匹配单行
Out[5]: [' this is a comment ']

In [6]: p.findall(text2)  # . 点号不能匹配多行中的换行符,所以整体匹配失败
Out[6]: []

可以使用.|\n匹配所有字符,或者指定re.DOTALL标志位:

In [7]: p2 = re.compile(r'/\*((?:.|\n)*?)\*/')

In [8]: p2.findall(text2)
Out[8]: [' this is a\nmultiline comment ']

In [9]: p3 = re.compile(r'/\*(.*?)\*/', re.DOTALL)

In [10]: p3
Out[10]: re.compile(r'/\*(.*?)\*/', re.DOTALL|re.UNICODE)

In [11]: p3.findall(text2)
Out[11]: [' this is a\nmultiline comment ']
未经允许不得转载: LIFE & SHARE - 王颜公子 » Python 正则表达式 re 模块

分享

作者

作者头像

Madman

如果博文内容有误或其它任何问题,欢迎留言评论,我会尽快回复; 或者通过QQ、微信等联系我

0 条评论

暂时还没有评论.

发表评论前请先登录