目录
  1. 1. re 模块
  2. 2. 通配符
  3. 3. 反斜杠
  4. 4. 字符类
  5. 5. 重复匹配
  6. 6. 特殊符号及用法
  7. 7. 元字符
  8. 8. 贪婪和非贪婪
  9. 9. 编译正则表达式
  10. 10. 实用的方法
    1. 10.1. search()
    2. 10.2. 匹配对象
    3. 10.3. findall()
    4. 10.4. 其他
正则表达式

正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配,但是正则表达式不容易,“如果你有一个问题需要用正则表达式解决,那就是两个问题了”

re 模块

Python 通过 re 模块来使用正则表达式。下面通过举例来学习吧

1
2
3
>>> import re
>>> re.search(r'Eason', 'Kailey likes Eason')
<re.Match object; span=(13, 18), match='Eason'>

search() 方法用于字符串中 搜索正则表达式模式第一次出现的位置,要注意两点:

  • 第一个参数是正则表达式模式,也就是要描述的搜索规则,最好使用原始字符串,可以避免麻烦
  • 找到后返回的范围是以下标为 0 开始的,找不到就返回 None

通配符

上面 search() 实现的功能用字符串的 find() 同样能实现。但是正则表达式有所谓的通配符:点号(.),它可以 匹配除换行符之外的任何字符,且只匹配一个字符

1
2
3
>>> import re
>>> re.search(r'like.', 'Kailey likes Eason')
<re.Match object; span=(7, 12), match='likes'>
  • 通常的通配符有 *(匹配 0/多 个字符) 和 ?(匹配 0/1 个字符)

反斜杠

如果我想搜索 通配符. 怎么办?与字符串相同,正则表达式只要在 元字符(有特殊能力的字符) 前面加上 反斜杠\,就能消除它的特殊功能。
同时,反斜杠\ 也可以赋予普通字符超能力。比如,\d 代表者数字,因此我们可以这样匹配 IP 地址

1
2
>>> re.search(r'\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d', 'qweqww4280192.168.123.1231hgw99y13yha')
<re.Match object; span=(10, 25), match='192.168.123.123'>

当然上面这样写是有问题的:

  • \d 匹配 0-9 的数字,\d\d\d 匹配 000-999 的数字,而 IP 地址的范围是 0-255
  • 这里要求 IP 地址每个部分都是 3 位数字,现实生活中并不都是,如 192.168.0.1

字符类

为了表示一个字符的范围,可以创建一个 字符类。使用 中括号[] 将任何内容包起来就是一个字符类,它的含义是 只要是这个字符类中的任何字符,结果就算匹配
举个例子,比如想要匹配元音字母,可以这样做:

1
2
>>> re.search(r'[aeiou]', 'qweqww4280192.168.123.4561hgw99y13yha')
<re.Match object; span=(2, 3), match='e'>

但是这样子并不会识别大写的元音字母,我们有两种解决方法:

  1. 关闭大小写敏感模式
  2. 修改字符类

这里先说修改字符类,我们只需把 [aeiou] 改成 [aeiouAEIOU] 即可匹配大小写的元音字母。
在字符串中,还可以使用 小横杠(-)表示范围:[a-z](所有小写字母)
同样也可以表示 数字范围:[0-2][0-5][0-5](000-255)

重复匹配

范围问题解决了,我们开始解决另一个问题——匹配个数。
使用 大括号{} 来实现重复匹配的功能:

1
2
>>> re.search(r'ab{3}c', 'abc abbbc')
<re.Match object; span=(4, 9), match='abbbc'>

重复次数也可以取一个范围:{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
2
>>> re.search(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.Match object; span=(0, 15), match='192.168.255.255'>
  • 小括号() 表示分组,表示这个小组是一个整体。后面再加上 {3} 表示这个小组重复匹配 3 次。
  • 注意不要忘记 IP 地址间的 点号.

现在开始考虑位数了,因为有些部分不是三位数,那要怎么办呢?重复次数为 0 !现在,我们可以真正匹配一个合格的 IP 地址了:

1
2
>>> re.search(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.Match object; span=(5, 16), match='192.168.0.1'>

特殊符号及用法

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
2
3
>>> s = "<html><title>Easonzzzz</title></html>"
>>> re.search('<.+>', s)
<re.Match object; span=(0, 37), match='<html><title>Easonzzzz</title></html>'>

这段代码原本只想匹配 <html>,但是由于贪婪模式,直接匹配了整个字符串,那怎么关闭贪婪模式,或者说启用非贪婪模式。
只需在表示重复的元字符后面加上 一个问号(?)

1
2
>>> re.search('<.+?>', s)
<re.Match object; span=(0, 6), match='<html>'>

编译正则表达式

如果需要重复使用某个正则表达式,那么可以先将正则表达式编译成模式对象。
使用 re.compile() 方法来进行编译:

1
2
3
4
5
>>> p = re.compile("[A-Z]")
>>> p.search("EasonZzZzZz")
<re.Match object; span=(0, 1), match='E'>
>>> p.findall("EasonZzZzZz")
['E', 'Z', 'Z', 'Z']

其中的 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
      9
      char_ref = re.compile(r"""
      &[#] # 开始数字引用
      (
      0[0-7]+ # 八进制
      | [0-9]+ # 十进制
      | x[0-9a-fA-F] # 十六进制
      )
      ; # 结尾分号
      """, re.VERBOSE)

实用的方法

模块级别的 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() 实现字符串的替换。

文章作者: EasonZzZz
文章链接: http://yoursite.com/2020/02/16/学习/Python学习/正则表达式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Nice To Meet U