re 正则表达式

有些人面临一个问题时会想:“我知道,我将使用正则表达式来解决这个问题。”这会让他们面临的问题变成了两个。 – Jamie Zawinski

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

模块 re 提供了对正则表达式的支持,在 Python 中使用正则表达式只需两个步骤,如下:

>>>  import re
>>>  nu = re.search(r'\d{3}-\d{3,4}-\d{4}', 'My number is 400-055-2818.')
>>>  nu.group(0)
400-055-2818

常用操作符

操作符就是上边定义中指出的“事先定义好的一些特定字符”,它们具有特殊的含义。如果在匹配时需要使用字符原本的含义,需要用 \ 转义字符转义。

操作符

含义

匹配结果

.

任何单个字符

[]

单个字符集范围

[ac] 匹配 a 或 c;[a-z] 匹配 a 至 z 中的单个字符

[^]

非单字符集范围

[^ab] 匹配非 a 或非 b 的单个字符

*

零次或无限次

abc* 匹配 ab、abc、abcc、abccc 等

+

一次或无限次

abc+ 匹配 abc、abcc、abccc 等

?

零次或一次

abc? 匹配 ab、abc

|

左右任意一个

abc|def 匹配 abc 或 def

{m}

m 次

ab{2}c 匹配 abbc

{m,n}

m 至 n 次

ab{1,4}c 匹配 abc、abbc、abbbc、abbbbc

^

字符串开头

^abc 匹配以 abc 开头的字符串 abc…

$

字符串结尾

abc$ 匹配以 abc 结尾的字符串 …abc

()

分组

(abc) 匹配 abc,(abc|def) 匹配 abc 或 def

\d

数字

等价于 [0-9]

\D

非数字

等价于 [^0-9]

\w

单词字符

等价于 [A-Za-z0-9_]

\W

非单词字符

等价于 [^A-Za-z0-9_]

\s

任意空白符

等价于 [ \f\n\r\t\v] 包括空格、制表符、换页符等

\S

非任意空白符

除空格、制表符和换行符以外的任何字符

非贪婪匹配

在字符串 ‘HaHaHaHaHa’ 中,要匹配 (Ha){3,5} 会返回什么结果呢?答案是 HaHaHaHaHa。

你可能会问,为什么会是 HaHaHaHaHa,而不是更短的 HaHaHa。毕竟,’HaHaHa’ 和 ‘HaHaHaHa’ 都能够有效地匹配正则表达式 (Ha){3,5}。

Python 的正则表达式默认是“贪婪”的,这表示在有歧义的情况下,它会尽可能长的匹配字符串。那么,如何才能尽可能短的匹配字符串呢?只需要多加一个 ? 问号。

注意在查找相同字符串时,花括号的贪婪形式和非贪婪形式之间的区别:

>>>  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'

请注意,问号在正则表达式中可能有两种含义:声明非贪婪匹配或表示可选的分组。这两种含义是完全无关的。

最小匹配操作符组合实例:

操作符

说明

*?

最小匹配 0 次或无限次

+?

最小匹配 1 次或无限次

??

最小匹配 0 次或 1 次

{m,n}?

最小匹配 m 至 n 次(包含 n 次)

常用方法

方法

描述

re.findall()

搜索字符串,返回列表类型

re.finditer()

搜索字符串,返回匹配结果的迭代类型,每个迭代元素是 matche 对象

re.match()

从字符串的开始位置起匹配正则表达式,返回 matche 对象

re.search()

在字符串中搜索匹配正则表达式的第一个位置,返回 matche 对象

re.split()

将一个字符串按照正则表达式匹配结果进行分割,返回列表类型

re.sub()

在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

re.findall(pattern, string, flags=0)

从左到右扫描字符串搜索匹配样式,将匹配结果按找到的顺序以列表的形式返回。如果样式里存在一到多个组,就返回一个组合列表。空匹配也会包含在结果里。

re.finditer(pattern, string, flags=0)

匹配样式在字符串中所有的非重复匹配,返回一个迭代器。字符串从左到右扫描,匹配按顺序排列。空匹配也包含在结果里。

re.match(pattern, string, flags=0)

从字符串开始位置匹配样式,就返回一个相应的匹配对象。如果没有匹配,就返回 None ;注意它跟零长度匹配是不同的。

  • pattern:正则表达式语句

  • string:待匹配字符串

  • flags:正则表达式的控制标记

常用标记

说明

re.A re.ASCII

让 \w、\W、\b、\B、\d、\D、\s 和 \S 只匹配 ASCII,而不是 Unicode。

re.I re.IGNORECASE

忽略大小写

re.S re.DOTALL

. 操作符匹配任意字符,默认匹配除换行符外的其他任意字符

re.search(pattern, string, flags=0)

扫描整个字符串搜索匹配样式的第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回一个 None ;注意这和找到一个零长度匹配是不同的。

Note

search() 和 match()

  • re.match() 检查字符串开头

  • re.search() 检查字符串的任意位置(默认Perl中的行为)

例如:

>>> re.match("c", "abcdef")    # No match
>>> re.search("c", "abcdef")   # Match
<re.Match object; span=(2, 3), match='c'>

在 search() 中,可以用 ‘^’ 作为开始来限制匹配到字符串的首位

>>> re.match("c", "abcdef")    # No match
>>> re.search("^c", "abcdef")  # No match
>>> re.search("^a", "abcdef")  # Match
<re.Match object; span=(0, 1), match='a'>

注意 MULTILINE 多行模式中函数 match() 只匹配字符串的开始,但使用 search() 和以 ‘^’ 开始的正则表达式会匹配每行的开始

>>> re.match('X', 'A\nB\nX', re.MULTILINE)  # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE)  # Match
<re.Match object; span=(4, 5), match='X'>

re.split(pattern, string, maxsplit=0, flags=0)

用匹配样式分割字符串。如果在匹配样式中捕获到括号,那么所有的组里的文字也会包含在列表里。

  • maxsplit:最大分割数,剩余部分将全部放入列表的最后一个元素

re.sub(pattern, repl, string, count=0, flags=0)

返回通过使用 repl 替换后的新字符串。如果样式没有找到,则返回原字符串。

  • repl:字符串替换中的新字符串。repl 可以是字符串或函数

  • count:匹配的最大替换次数

repl 为字符串,则其中任何反斜杠转义序列都会被处理。也就是说, \n 会被转换为一个换行符, \r 会被转换为一个回车附,依此类推。其他未知转义序列例如 \& 会保持原样。向后引用像是 \6 会用样式中第 6 组所匹配到的子字符串来替换。例如:

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

repl 为一个函数,那它会对每个非重复的匹配样式的情况调用。这个函数只能有一个匹配对象参数,并返回一个替换后的字符串。比如

>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

样式可以是一个字符串或者一个样式对象。

可选参数 count 是要替换的最大次数;count 必须是非负整数。如果忽略这个参数,或者设置为 0,所有的匹配都会被替换。

匹配对象

匹配对象总是有一个布尔值 True。如果没有匹配的话 match() 和 search() 返回 None 所以你可以简单的用 if 语句来判断是否匹配

match = re.search(pattern, string)

if match:

process(match)

匹配对象支持以下方法和属性:

Match.expand(template)

对 template 进行反斜杠转义替换并且返回,就像 sub() 方法中一样。转义如同 n 被转换成合适的字符,数字引用(1, 2)和命名组合(g<1>, g<name>) 替换为相应组合的内容。

在 3.5 版更改: 不匹配的组合替换为空字符串。

Match.group([group1, …])

返回一个或者多个匹配的子组。如果只有一个参数,结果就是一个字符串,如果有多个参数,结果就是一个元组(每个参数对应一个项),如果没有参数,组1默认到0(整个匹配都被返回)。 如果一个组N 参数值为 0,相应的返回值就是整个匹配字符串;如果它是一个范围 [1..99],结果就是相应的括号组字符串。如果一个组号是负数,或者大于样式中定义的组数,一个 IndexError 索引错误就 raise。如果一个组包含在样式的一部分,并被匹配多次,就返回最后一个匹配。: >>>

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')

如果正则表达式使用了 (?P<name>…) 语法, groupN 参数就也可能是命名组合的名字。如果一个字符串参数在样式中未定义为组合名,一个 IndexError 就 raise。

一个相对复杂的例子 >>>

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

命名组合同样可以通过索引值引用 >>>

>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'

如果一个组匹配成功多次,就只返回最后一个匹配 >>>

>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'

Match.__getitem__(g)

这个等价于 m.group(g)。这允许更方便的引用一个匹配 >>>

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0]       # The entire match
'Isaac Newton'
>>> m[1]       # The first parenthesized subgroup.
'Isaac'
>>> m[2]       # The second parenthesized subgroup.
'Newton'

3.6 新版功能.

Match.groups(default=None)

返回一个元组,包含所有匹配的子组,在样式中出现的从1到任意多的组合。 default 参数用于不参与匹配的情况,默认为 None。

例如 >>>

>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')

如果我们使小数点可选,那么不是所有的组都会参与到匹配当中。这些组合默认会返回一个 None ,除非指定了 default 参数。 >>>

>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')

Match.groupdict(default=None)

返回一个字典,包含了所有的 命名 子组。key就是组名。 default 参数用于不参与匹配的组合;默认为 None。 例如 >>>

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}

Match.start([group]) Match.end([group])

返回 group 匹配到的字串的开始和结束标号。group 默认为0(意思是整个匹配的子串)。如果 group 存在,但未产生匹配,就返回 -1 。对于一个匹配对象 m, 和一个未参与匹配的组 g ,组 g (等价于 m.group(g))产生的匹配是

m.string[m.start(g):m.end(g)]

注意 m.start(group) 将会等于 m.end(group) ,如果 group 匹配一个空字符串的话。比如,在 m = re.search(‘b(c?)’, ‘cba’) 之后,m.start(0) 为 1, m.end(0) 为 2, m.start(1) 和 m.end(1) 都是 2, m.start(2) raise 一个 IndexError 例外。

这个例子会从email地址中移除掉 remove_this >>>

>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'

Match.span([group])

对于一个匹配 m , 返回一个二元组 (m.start(group), m.end(group)) 。 注意如果 group 没有在这个匹配中,就返回 (-1, -1) 。group 默认为0,就是整个匹配。

Match.pos

pos 的值,会传递给 search() 或 match() 的方法 a 正则对象 。这个是正则引擎开始在字符串搜索一个匹配的索引位置。

Match.endpos

endpos 的值,会传递给 search() 或 match() 的方法 a 正则对象 。这个是正则引擎停止在字符串搜索一个匹配的索引位置。

Match.lastindex

捕获组的最后一个匹配的整数索引值,或者 None 如果没有匹配产生的话。比如,对于字符串 ‘ab’,表达式 (a)b, ((a)(b)), 和 ((ab)) 将得到 lastindex == 1 , 而 (a)(b) 会得到 lastindex == 2 。

Match.lastgroup

最后一个匹配的命名组名字,或者 None 如果没有产生匹配的话。

Match.re

返回产生这个实例的 正则对象 , 这个实例是由 正则对象的 match() 或 search() 方法产生的。

Match.string

传递到 match() 或 search() 的字符串。

在 3.7 版更改: 添加了对 copy.copy() 和 copy.deepcopy() 的支持。匹配对象被看作是原子性的。

编译正则表达式

在 Python 中使用正则表达式有几个步骤,但每一步都相当简单。

  1. 用 import re 导入正则表达式模块。

  2. 用 re.compile() 函数创建一个 Regex 对象(记得使用原始字符串)。

  3. 向 Regex 对象的 search() 方法传入想查找的字符串。它返回一个 Match 对象。

  4. 调用 Match 对象的 group() 方法,返回实际匹配文本的字符串。

>>>  import re
>>>  phoneNumRegex = re.compile(r'\d{3}-\d{3}-\d{4}')
>>>  mo = phoneNumRegex.search('My number is 415-555-4242.')
>>>  print('Phone number found: ' + mo.group())
Phone number found: 415-555-4242

编译正则表达式

正则表达式被编译成模式对象,模式对象具有各种操作的方法,例如搜索模式匹配或执行字符串替换。

re.compile() 也接受一个可选的 flags 参数,用于启用各种特殊功能和语法变体。 我们稍后将介绍可用的设置,但现在只需一个例子 >>>

>>> p = re.compile('ab*', re.IGNORECASE)

正则作为字符串传递给 re.compile() 。 正则被处理为字符串,因为正则表达式不是核心Python语言的一部分,并且没有创建用于表达它们的特殊语法。 (有些应用程序根本不需要正则,因此不需要通过包含它们来扩展语言规范。)相反,re 模块只是Python附带的C扩展模块,就类似于 socket 或 zlib 模块。

将正则放在字符串中可以使 Python 语言更简单,但有一个缺点是下一节的主题。

re.compile(pattern, flags=0)

将正则表达式的样式编译为一个正则表达式对象(正则对象),可以用于匹配,通过这个对象的方法 match(),search() 以及其他如下描述。

这个表达式的行为可以通过指定标记的值来改变。值可以是以下任意变量,可以通过位的 OR 操作来结合(| 操作符)。

序列

prog = re.compile(pattern) result = prog.match(string)

等价于

result = re.match(pattern, string)

如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效。

Note

通过 re.compile() 编译后的样式,和模块级的函数会被缓存,所以少数的正则表达式使用无需考虑编译的问题。