正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配,但是正则表达式不容易,“如果你有一个问题需要用正则表达式解决,那就是两个问题了”
re 模块
Python 通过 re 模块来使用正则表达式。下面通过举例来学习吧
1 | import re |
search() 方法用于字符串中 搜索正则表达式模式第一次出现的位置,要注意两点:
- 第一个参数是正则表达式模式,也就是要描述的搜索规则,最好使用原始字符串,可以避免麻烦
- 找到后返回的范围是以下标为 0 开始的,找不到就返回 None
通配符
上面 search() 实现的功能用字符串的 find() 同样能实现。但是正则表达式有所谓的通配符:点号(.),它可以 匹配除换行符之外的任何字符,且只匹配一个字符
1 | import re |
- 通常的通配符有 *(匹配 0/多 个字符) 和 ?(匹配 0/1 个字符)
反斜杠
如果我想搜索 通配符. 怎么办?与字符串相同,正则表达式只要在 元字符(有特殊能力的字符) 前面加上 反斜杠\,就能消除它的特殊功能。
同时,反斜杠\ 也可以赋予普通字符超能力。比如,\d 代表者数字,因此我们可以这样匹配 IP 地址
1 | r'\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d', 'qweqww4280192.168.123.1231hgw99y13yha') re.search( |
当然上面这样写是有问题的:
- \d 匹配 0-9 的数字,\d\d\d 匹配 000-999 的数字,而 IP 地址的范围是 0-255
- 这里要求 IP 地址每个部分都是 3 位数字,现实生活中并不都是,如 192.168.0.1
字符类
为了表示一个字符的范围,可以创建一个 字符类。使用 中括号[] 将任何内容包起来就是一个字符类,它的含义是 只要是这个字符类中的任何字符,结果就算匹配
举个例子,比如想要匹配元音字母,可以这样做:
1 | r'[aeiou]', 'qweqww4280192.168.123.4561hgw99y13yha') re.search( |
但是这样子并不会识别大写的元音字母,我们有两种解决方法:
- 关闭大小写敏感模式
- 修改字符类
这里先说修改字符类,我们只需把 [aeiou] 改成 [aeiouAEIOU] 即可匹配大小写的元音字母。
在字符串中,还可以使用 小横杠(-) 来 表示范围:[a-z](所有小写字母)
同样也可以表示 数字范围:[0-2][0-5][0-5](000-255)
重复匹配
范围问题解决了,我们开始解决另一个问题——匹配个数。
使用 大括号{} 来实现重复匹配的功能:
1 | r'ab{3}c', 'abc abbbc') re.search( |
重复次数也可以取一个范围:{3,5} 表示重复次数 3-5 次。
现在会了重复匹配,但是数字范围还没解决呢。
[0-255] 其实是 [0-2 5 5],表示 0-2 和 5,匹配 0,1,2,5,只匹配一位数字
[0-2][0-5][0-5] 也不是表示 0-255,比如 188 就表示不了
现在我们要怎么表示 0-255呢?要用上 或| 了。
[0-1]\d\d|2[0-4]\d|25[0-5]
0或1开头;2开头;25开头
- 注意:或| 左右不能有空格,否则会被当作空格符去匹配
我们现在可以试试匹配每部分都是三位数的 IP 地址了:
1 | r'(([0-1]\d\d|2[0-4]\d|25[0-5])\.){3}([0-1]\d\d|2[0-4]\d|25[0-5])', '192.168.255.255') re.search( |
- 小括号() 表示分组,表示这个小组是一个整体。后面再加上 {3} 表示这个小组重复匹配 3 次。
- 注意不要忘记 IP 地址间的 点号.
现在开始考虑位数了,因为有些部分不是三位数,那要怎么办呢?重复次数为 0 !现在,我们可以真正匹配一个合格的 IP 地址了:
1 | r'(([0-1]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])\.){3}([0-1]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])', 'other192.168.0.1other') re.search( |
特殊符号及用法
Python 中的正则表达式也是以字符串的形式描述的。
正则表达式的强大之处在于 特殊符号的应用,特殊符号定义了 字符集合、子组匹配、模式重复次数。正是这些特殊符号,使得一个正则表达式可以匹配一个复杂的规则。
下表列举了 Python3 正则表达式特殊符号及用法
字符 | 含义 |
---|---|
. | 1. 表示匹配除了换行符以外的任何字符。 2. 通过设置 re.DOTALL 标志可以使 点. 匹配所有字符(包括换行符)。 |
| | A|B,表示匹配正则表达式 A 或者 B。 |
^ | 1. 匹配输入字符串的开始位置。 2.如果设置了 re.MULTILINE 标志,^ 也可以匹配换行符之后的位置。 |
$ | 1. 匹配输入字符串的结束位置。 2. 如果设置了 re.MULTILINE 标志,$ 也可以匹配换行符之前的位置。 |
\ | 1. 将一个普通字符变成特殊字符。 2. 解除元字符的特殊功能 引用序号对应的子组所匹配的字符串。 |
[…] | 1. 字符类,匹配所包含的任意一字符。 2. 连字符(-) 如果出现在字符串中间表示字符范围描述;如果出现在首位则仅作为普通字符。 3. 特殊字符仅有反斜线()保持特殊含义,用于转义字符;其他特殊字符,如 *、+、? 等均作为普通字符处理。 4. 脱字符(^) 如果出现在首位则表示匹配不包含其中的任意字符;如果出现在中间就仅作普通字符匹配 |
{M,N} | M 和 N 均为非负整数,其中 M<=N,表示前面的 RE 匹配 M~N 次。 注:{M,} 表示至少匹配 M 次;{,N} 等价于 {0,N} 表示至多匹配 N 次 |
* | 匹配前面的子表达式零次或多次,等价于 {0,} |
+ | 匹配前面的子表达式一次或多次,等价于 {1,} |
? | 匹配前面的子表达式零次或一次,等价于 {0,1} |
*?,+?,?? | 默认境况下,*、+、? 的匹配模式是贪婪模式(会尽可能多地匹配符合规则的字符串);*?,+?,?? 分别对应启用对应的非贪婪模式 。 例如:“Easonzzzz”,‘Easonz+’ 会匹配整个字符串,而 ‘Easonz+?’ 则只匹配 ‘Easonz’ |
{M,N}? | 启动非贪婪模式,既只匹配 M 次 |
(…) | 匹配小括号中的正则表达式,或者指定一个子组的开始和结束位置。 注:子组的内容可以在匹配之后被 “\数字” 再次引用。 例如:(\w+) \1 会匹配 “Eason Easonzzzz” 中的 ‘Eason Eason’ |
(?..) | (? 开头的表示为正则表达式的扩展语法 |
下表列举了 ‘\’ 和另一个字符组成的特殊含义
字符 | 含义 |
---|---|
\序号 | 1. 引用对应的子组所匹配的字符串,子组的序号从 1 开始计算。 2. 如果序号是以 0 开头,或者 3 个数字的长度,那么不会被用于引用对应的子组,而是用于匹配八进制数字所表示的 ASCII 码对应的字符。 |
\A | 匹配输入字符串的开始位置 |
\Z | 匹配输入字符串的结束位置 |
\b | 匹配一个单词边界,单词被定义为 Unicode 的字母数字或下划线字符 |
\B | 匹配非单词边界,与 \b 相反 |
\d | 1. 对于 Unicode(str类型)模式:匹配任何一个数字,包括 [0-9] 和其他数字字符;如果开启了 re.ASCII 标志,就匹配 [0-9]。 2. 对于 8 位(bytes类型)模式:匹配 [0-9] 中任何一个数字 |
\D | 匹配任何非 Unicode 的数字,与 \d 相反;如果开启了 re.ASCII 标志,就匹配 [^0-9]。 |
\s | 1. 对于 Unicode(str类型)模式:匹配 Unicode 中的空白字符(包括 [\t\n\r\f\v] 以及其他空白字符);如果开启了 re.ASCII 标志,就匹配 [\t\n\r\f\v]。 2. 对于 8 位(bytes类型)模式:只匹配 [\t\n\r\f\v] |
\S | 匹配任何非 Unicode 中的空白字符,其实就是与 \s 相反;如果开启了 re.ASCII 标志,就相当于匹配 [^\t\n\r\f\v] |
\w | 对于 Unicode(str类型)模式:匹配 Unicode 中的单词字符,基本所有的语言的字符都可以匹配,当然包括数字和下划线;如果开启了 re.ASCII 标志,只匹配 [a-zA-Z0-9]。 |
\W | 匹配任何非 Unicode 的单词字符,与 \w 相反;如果开启了 re.ASCII 标志,匹配 [^a-zA-Z0-9]。 |
正则表达式还支持大部分 Python 字符串的转义符号:\a,\b,\f,\n,\r,\t,\u,\U,\v,\x,\\。注意:
- \b 通常用于匹配一个单词边界,只有在字符类中才表示“退格”
- \u 和 \U 只有在 Unicode 模式下才被识别
- 八进制转义(\数字)是有限制的,如果第一个数字是 0 或者有 3 个八进制数字,那么就被认为是八进制数;其他情况被认为是子组引用;至于字符串,八进制转义总是最多只能是 3 个数字的长度
下表列举了 Python 支持的所有扩展语法
字符 | 含义 |
---|---|
(?aiLmsux) | 1. (? 后可以紧跟着 ‘a’,‘i’,‘L’,‘m’,‘s’,‘u’,‘x’ 中的一个或多个字符,只能在正则表达式的开头使用。 2. 每一个字符对应一种匹配标志:re-A(只匹配 ASCII 字符),re-I(忽略大小写),re-L(区域设置),re-M(多行模式), re-S(. 匹配任何符号),re-X(详细表达式),包含这些字符将会影响整个正则表达式的规则 3. 当你不想通过 re.compile() 设置正则表达式标志,这种方法就非常有用啦。 注意,由于 (?x) 决定正则表达式如何被解析,所以它应该总是被放在最前边(最多允许前边有空白符)。如果 (?x) 的前边是非空白字符,那么 (?x) 就发挥不了作用了。 |
(?..) | 非捕获组,即该子组匹配的字符串无法从后边获取 |
(?P<name>…) | 命名组,通过组的名字(name)即可访问到子组匹配的字符串 |
(?P=name) | 反向引用一个命名组,它匹配指定命名组匹配的任何内容 |
(?#…) | 注释,括号中的内容将被忽略 |
(?=…) | 前向肯定断言。如果当前包含的正则表达式(这里以 … 表示)在当前位置成功匹配,则代表成功,否则失败。一旦该部分正则表达式被匹配引擎尝试过,就不会继续进行匹配了;剩下的模式在此断言开始的地方继续尝试。 举个栗子:Eason(?=zz) 只匹配后边紧跟着 “zz” 的字符串 “Eason” |
(?!..) | 前向否定断言。这跟前向肯定断言相反(不匹配则表示成功,匹配表示失败)。 举个栗子:Eason(?!zz) 只匹配后边不是 “zz” 的字符串 “Eason” |
(?<=…) | 后向肯定断言。跟前向肯定断言一样,只是方向相反。 举个栗子:(?<=Eason)zz 只匹配前边紧跟着 “Eason” 的字符串 “zz” |
(?<!..) | 后向否定断言。跟前向肯定断言一样,只是方向相反。 举个栗子:(?<!Eason)zz 只匹配前边不是 “Eason” 的字符串 “zz” |
(?(id/name)yes-pattern|no-pattern) | 1. 如果子组的序号或名字存在的话,则尝试 yes-pattern 匹配模式;否则尝试 no-pattern 匹配模式。 2. no-pattern 是可选的。 |
元字符
以下是正则表达式所有的元字符,它们各自有特殊的含义:
. ^ $ * + ? {} [] \ | ()
- 点号(.):表示匹配除换行符外的任何字符
- 管道符(|):有点类似于 逻辑或 操作
- 脱字符(^):表示匹配字符串的 开始位置(从开始位置开始匹配)
- 美元字符($):表示匹配字符串的 结束位置,也就是只有目标字符出现在末尾才匹配
- 反斜杠(\):既能让一个普通字符特殊化,也能接触元字符的特殊功能
- 如果反斜杠后面跟着数字,它还有两种用法:
- 如果跟着 1~99,那么它表示引用序号对应的子组所匹配的字符串
- 如果跟着 以 0 开头或者是三位数字,那么它是一个八进制数,代表对应的 ASCII 字符
- 如果反斜杠后面跟着数字,它还有两种用法:
- 小括号():本身是一对元字符,被它们括起来的称为一个 子组
- 子组是一个整体,可以在后面对它引用,以 1 开始,不是零基
- 中括号[]:可以说生成一个字符类,其实就是一个字符集合,被它包围的元字符都失去了特殊功能,除了反斜杠(\)。
- 常和 连字符(-) 使用,表示范围。如果放在开头会,被当作普通字符处理
- 脱字符(^) 放在首位表示取反
- 大括号{}:用于表示重复。
- 重复次数可以是一个数字,也可以是一个范围
- 星号*,加号(+),问号(?):也表示重复,分别是 *: {0,};+: {1,};?: {0,1}
- 推荐使用这三个,因为进行了内部优化,效率更高
贪婪和非贪婪
正则表达式默认是启用贪婪的匹配模式,也就是说:只要是条件符合的情况下,会尽量多地匹配。举个栗子:
1 | "<html><title>Easonzzzz</title></html>" s = |
这段代码原本只想匹配 <html>,但是由于贪婪模式,直接匹配了整个字符串,那怎么关闭贪婪模式,或者说启用非贪婪模式。
只需在表示重复的元字符后面加上 一个问号(?):
1 | '<.+?>', s) re.search( |
编译正则表达式
如果需要重复使用某个正则表达式,那么可以先将正则表达式编译成模式对象。
使用 re.compile() 方法来进行编译:
1 | "[A-Z]") p = re.compile( |
其中的 p 就是模式对象 <class ‘re.Pattern’>。之前使用的 search() 和 findall() 都是它的内建函数。
此外,我们还可以正则表达式的工作方式。
下表列举了可以使用的编译标志
标志 | 含义 |
---|---|
ASSCII, A | 使得转义符号只能匹配 ASCII 字符 |
DOTALL, S | 使得(.)匹配任何符号,包括换行符 |
IGNORECASE, I | 匹配时不区分大小写 |
LOCALE, L | 支持当前的语言(区域)设置 |
MULTILINE, M | 多行匹配,影响 ^ 和 $ |
VERBOX, X (for ‘entended’) |
启用详细的正则表达式 |
- A(ASCII)
- 使得 \w,\W,\b,\B,\s,\S 只匹配 ASCII 字符,而不匹配完整的 Unicode 字符。这个标志仅对 Unicode 模式有意义,并忽略字节模式
- S(DOTALL)
- 使得 点号(.) 匹配任何字符,包括换行符。不启用的话,点号(.) 匹配除了换行符的所有字符
- I(IGNORECASE)
- 字符类和文本字符串在匹配时不区分大小写。
- 不设置 LOCALE 的话,不考虑语言(区域)设置这方面的大小写问题
- L(LOCALE)
- 使得 \w,\W,\b,\B 依赖当前的语言(区域)环境,而不是 Unicode 数据库
- M(MULTILINE)
- 通常 ^ 和 $ 只匹配字符串的开头和结尾。如果这个标志被设置的话,它们还匹配每一行的行首和行尾
- X(VERBOSE)
- 可以使正则表达式更加好看、有条理,因为这个标志可以使空格被忽略(除了出现在字符类中和使用反斜杠转义的空格)
- 这个表示还允许使用注释,井号(#)后面的内容是注释(当然,也除了字符类中和转义的 \#)
1
2
3
4
5
6
7
8
9char_ref = re.compile(r"""
&[#] # 开始数字引用
(
0[0-7]+ # 八进制
| [0-9]+ # 十进制
| x[0-9a-fA-F] # 十六进制
)
; # 结尾分号
""", re.VERBOSE)
实用的方法
search()
模块级别的 search() 方法就是直接调用 re.search(),编译后的正则表达式模式对象也同样拥有 search() 方法。
re.search(pattern, string, flags=0)
regex.search(string[, pos[, endpos]])
由于 flags 在编译的时候就同时编译进去了,所以 regex.search 不需要 flags。另外 regex.search 还支持设置搜索的开始和结束位置。
匹配对象
search() 并不会立刻返回可以使用的字符串,而是返回一个匹配对象。我们需要使用匹配对象的一些方法才能获得需要的内容。
- 使用 group() 方法可以获取匹配的字符串。还可以设置序号来访问对应的子组捕获的内容
- start()、end() 和 span() 分别返回匹配的开始位置、结束位置和匹配的范围。
findall()
如果正则表达式没有包含子组的话,findall() 方法只是找出所有匹配的内容,然后组织成列表返回。
但是如果正则表达式包含一个或者多个子组,就会返回子组中匹配的内容;如果存在多个子组,那么就会将匹配的内容组成元组的形式返回。
其他
另外还有一些实用的方法,例如:finditer() 将结果返回一个迭代器;sub() 实现字符串的替换。