Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Python中的正则匹配

2022/05/11 Python
Word count: 4,157 | Reading time: 16min

Python中的正则匹配

虽然正则早就会用了,但是有些使用方法老是忘记,因此还是写篇记录一下

特殊字符

特别字符 描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 $。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 *。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 [。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 ?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\’ 匹配 “”,而 ‘(’ 则匹配 “(”。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 ^。
{ 标记限定符表达式的开始。要匹配 {,请使用 {。
| 指明两项之间的一个选择。要匹配 |,请使用 |。

特殊字符

所谓特殊字符,就是一些有特殊含义的字符,如上面说的 runoo*b 中的 *****,简单的说就是表示任何字符串的意思。如果要查找字符串中的 ***** 符号,则需要对 ***** 进行转义,即在其前加一个 ***: runo*ob 匹配 runoob。

许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符"转义",即,将反斜杠字符**** 放在它们前面。下表列出了正则表达式中的特殊字符:

特别字符 描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 $。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 *。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 [。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 ?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\’ 匹配 “”,而 ‘(’ 则匹配 “(”。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 ^。
{ 标记限定符表达式的开始。要匹配 {,请使用 {。
| 指明两项之间的一个选择。要匹配 |,请使用 |。

*、+限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?就可以实现非贪婪或最小匹配。

限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 ***** 或 +?{n}{n,}{n,m} 共6种。

正则表达式的限定符有:

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 、 “does” 中的 “does” 、 “doxy” 中的 “do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

定位符

定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。

定位符用来描述字符串或单词的边界,^$ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。

正则表达式的定位符有:

字符 描述
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。
\b 匹配一个单词边界,即字与空格间的位置。
\B 非单词边界匹配。

捕获与非捕获

用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但用圆括号会有一个副作用,使相关的匹配会被缓存,此时可用?:放在第一个选项前来消除这种副作用。

其中 ?: 是非捕获元之一,还有两个非捕获元是 ?=?!,这两个还有更多的含义,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。

选择

(?(id/name)yes|no)的说明:“Matches yes pattern if the group with id/name matched,the (optional) no pattern otherwise.”,即如果前面的捕获组成功匹配,则执行yes的匹配,否则执行no的匹配

1
2
3
4
5
6
7
8
9
10
11
abcabc

>>>print(re.search(r'(\d)?abc(?(1)\d|abc)','abcabc'))
<_sre.SRE_Match object; span=(0, 6), match='abcabc'> #匹配成功


>>>print(re.search(r'(\d)+abc(?(1)\d|abc)','abcabc'))
None #匹配失败

>>>print(re.search(r'(\d)*abc(?(1)\d|abc)','abcabc'))
<_sre.SRE_Match object; span=(0, 6), match='abcabc'> #匹配成功

(?(R)yes|no)

1
2

If recursion of the whole pattern is successful, match the pattern before the | otherwise match the pattern after the |.

(?(?=...)yes|no)

If the lookahead succeeds, matches the pattern before the vertical bar. Otherwise, matches the pattern after the vertical bar. The lookaround can be negative also. Global flag breaks conditionals.

1
2
3
/(?(?=is)(is delicious)|(disgusting))/
Candy is delicious or disgusting.
// 成功匹配is delicious

(?(?<=...)yes|no)

If the lookbehind succeeds, match the pattern before the vertical bar. Otherwise, matches the pattern after the vertical bar. The lookaround can be negative. Global flag, ‘g’, breaks conditionals.

1
2
3
/(?(?<=\s)(delish)|(ew))/
Is candy delish or ew?
// 匹配delish

反向引用

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

可以使用非捕获元字符 ?:?=?! 来重写捕获,忽略对相关匹配的保存。

反向引用的最简单的、最有用的应用之一,是提供查找文本中两个相同的相邻单词的匹配项的能力。

正则 ?<= 和 ?= 用法](https://www.cnblogs.com/whaozl/p/5462865.html)

方法1: 匹配,捕获(存储)

正则表达式:(?<=(href=")).{1,200}(?=(">))

解释:

(?<=(href=")) 表示 匹配以(href=")开头的字符串,并且**捕获(存储)**到分组中

(?=(">)) 表示 匹配以(">)结尾的字符串,并且**捕获(存储)**到分组中

方法2: 匹配,不捕获(不存储)

正则表达式:(?<=(**?:**href=")).{1,200}(?=(**?:**">))

​ 解释:(?<=(**?:href=")) 表示 匹配以(href=")开头的字符串,并且不捕获(不存储)**到分组中

​ (?=(?:">)) 表示 匹配以(">)结尾的字符串,并且**不捕获(不存储)**到分组中

(?:pattern) 非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
(?<=pattern) 非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern) 非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。这个地方不正确,有问题

分组语法 捕获
(exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name’exp)
(?:exp) 匹配exp,不捕获匹配的文本
位置指定

  • (?=exp) —— 前向肯定界定符: 匹配exp前面的位置

    • 1
      2
      3
      /foo(?=bar)/
      foobar foobaz
      成功匹配foo
  • (?<=exp) —— 后向肯定界定符:匹配exp后面的位置

    • 1
      2
      3
      /(?<=foo)bar/
      foobar fuubar
      成功匹配bar
  • (?!exp) —— 前向否定界定符:匹配后面跟的不是exp的位置

    • 1
      2
      3
      /foo(?!bar)/
      foobar foobaz
      成功匹配后面的foo
  • (?<!exp) —— 后向否定界定符:匹配前面不是exp的位置

    • 1
      2
      3
      /(?<!not )foo/
      not foo but foo
      成功匹配后面的foo

注释
(?#comment) 这种类型的组不对正则表达式的处理产生任何影响,只是为了提供让人阅读注释

我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面。

位置指定
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们用于指定一个位置,就像\b,^,$那样,因此它们也被称为零宽断言。最好还是拿例子来说明吧:

(?=exp)也叫零宽先行断言,它匹配文本中的某些位置,这些位置的后面能匹配给定的后缀exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如果在查找I’m singing while you’re dancing.时,它会匹配sing和danc。

(?<=exp)也叫零宽后行断言,它匹配文本中的某些位置,这些位置的前面能给定的前缀匹配exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})*\b。请仔细分析这个表达式,它可能不像你第一眼看出来的那么简单。

下面这个例子同时使用了前缀和后缀:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。

附录:

常见正则表达式

  • Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
  • 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
  • InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$
  • 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
  • 电话号码(“XXX-XXXXXXX”、“XXXX-XXXXXXXX”、“XXX-XXXXXXX”、“XXX-XXXXXXXX”、"XXXXXXX"和"XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
  • 身份证号(15位、18位数字):^\d{15}|\d{18}$
  • 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):[1][a-zA-Z0-9_]{4,15}$
  • 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):[2]\w{5,17}$
  • 日期格式:^\d{4}-\d{1,2}-\d{1,2}
  • 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
  • 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
  • 匹配{}之内的内容: \{([^}]+)\} (匹配两个括号中没有}的地方)and \{(?:.|\s)+\}(匹配括号中所有字符)

问题:

  • 匹配换行符以外的元素: var def = \{([^\n]+)\};

  • 大括号以内的元素:new_info_tmp = re.findall(r'def = (\{.*?\});', html, re.S)[0],通过re.S

    • \s:默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。
    • 想使用正则表达式来获取一段文本中的任意字符,写出如下匹配规则:(.*),因此会在换行符处断开。如果想要满足之前的需求可以写成([\s\S]*),这个会包含\n
  • Python中的re.S

    • 如果不使用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始。

      而使用re.S参数以后,正则表达式会将这个字符串作为一个整体,在整体中进行匹配。

sub介绍

Python 的 re 模块提供了re.sub用于替换字符串中的匹配项,sub是substitute表示替换。

  • pattern:该参数表示正则中的模式字符串;

  • repl:repl可以是字符串,也可以是可调用的函数对象;如果是字符串,则处理其中的反斜杠转义。如果它是可调用的函数对象,则传递match对象,并且必须返回要使用的替换字符串

  • string:该参数表示要被处理(查找替换)的原始字符串;

  • count:可选参数,表示是要替换的最大次数,而且必须是非负整数,该参数默认为0,即所有的匹配都会被替换;

  • flags:可选参数,表示编译时用的匹配模式(如忽略大小写、多行模式等),数字形式,默认为0。

附录

捕获顺序

1
2
3
4
5
import re
a = "my_tris1.xml"
res = re.search("(tris(\d+))", a)
# ('tris1', '1'), 即group(1)==tris1, group(2) == 1
print(res.groups())

编号优先外捕获,再内捕获

sub捕获

1
2
3
4
5
import re
a = "my_tris1.xml"
# 我们在sublime或者pycharm中, 替换都使用的是$1代表第一个捕获组, 但在re库中要用\\1
res = re.sub("(tris(\d+))", "trips\\1", a)
print(res.groups())

sub中repl传函数对象

匹配字符串中的数字加2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
'''
匹配字符串中的数字加2
'''

def addAge(match) ->str:
'''返回匹配的值加2'''
age = match.group()
return str(int(age)+2)

s = 'my age is 20'
# repl 如果它是可调用的函数对象,则传递match对象,并且必须返回要使用的替换字符串
x = re.sub(r'[\d]+', addAge, s)
print(x)
# my age is 22

  1. a-zA-Z ↩︎

  2. a-zA-Z ↩︎

Author: Mrli

Link: https://nymrli.top/2019/07/25/Python中的正则匹配/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Vim记忆
NextPost >
利用wireshark抓取TCP的整个过程分析[转]
CATALOG
  1. 1. Python中的正则匹配
    1. 1.1. 特殊字符
    2. 1.2. 特殊字符
    3. 1.3. 限定符
    4. 1.4. 定位符
    5. 1.5. 捕获与非捕获
    6. 1.6. 选择
    7. 1.7. 反向引用
    8. 1.8. 正则 ?<= 和 ?= 用法](https://www.cnblogs.com/whaozl/p/5462865.html)
    9. 1.9. 附录:
      1. 1.9.1. 常见正则表达式
    10. 1.10. sub介绍
  2. 2. 附录
    1. 2.1. 捕获顺序
    2. 2.2. sub捕获
    3. 2.3. sub中repl传函数对象