侧边栏壁纸
博主头像
soulballad博主等级

技术文章记录及总结

  • 累计撰写 169 篇文章
  • 累计创建 26 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

【Python】-11.Python正则表达式

soulballad
2020-09-18 / 0 评论 / 0 点赞 / 44 阅读 / 8,463 字
温馨提示:
本文最后更新于 2022-03-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 正则表达式语法

1.1 普通字符

普通字符是正则表达式中最基本的结构之一,要理解正则表达式自然也要从普通字符开始。

普通字符包括没有显示指定为元字符的所有可打印和不可打印字符,包括所有大写字母和小写字母、所有数字、所有标点符号和一些其他符号。

例如:

[0-9] 匹配所有数字

[a-z] 匹配所有小写字母

[yY]es 匹配 yes或Yes

1.2 字符转义

有些字符拥有特殊的含义,匹配该类字符时需要对其进行转义,转义使用 “\”。

例如:

[ \[ ] 匹配 [

[0\-9] 匹配 0、-、9 三个字符

1.3 元字符

元字符就是正则表达式中拥有特殊意义的字符

字符描述
\将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\' 匹配 "" 而 "(" 则匹配 "("。
^匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。
*匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
?匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。
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?'。请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符 (*, +, ?, , {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。
.匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。
(pattern)匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '(' 或 ')'。
(?:pattern)匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=pattern)正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。
x|y匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。
[xyz]字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz]负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。
[a-z]字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\cx匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
\d匹配一个数字字符。等价于 [0-9]。
\D匹配一个非数字字符。等价于 [^0-9]。
\f匹配一个换页符。等价于 \x0c 和 \cL。
\n匹配一个换行符。等价于 \x0a 和 \cJ。
\r匹配一个回车符。等价于 \x0d 和 \cM。
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S匹配任何非空白字符。等价于 [^\f\n\r\t\v]。
\t匹配一个制表符。等价于 \x09 和 \cI。
\v匹配一个垂直制表符。等价于 \x0b 和 \cK。
\w匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。
\W匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。
\xn匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,'\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。
\num匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。
\n标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
\nm标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。
\nml如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。

1.4 限定符

限定符指定输入中必须存在字符、组或字符类的多少个实例才能找到匹配项。上面的 “*”、“+”、“?”、“”、“{n,}” 和 “{n,m}” 都是限定符。

例如:

y{5} -> 匹配 yyyyy,3{2} -> 匹配 33,\w{3} -> 匹配三个字母
y{3,} -> 匹配 yyy,yyyy,yyyy...,[0-9]{3,} -> 匹配3位以上的数字
y{2,4} -> 匹配 yy,yyy,yyyy,[0-9]{8,11} -> 匹配 8-11 为数字

1.5 定位符

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

定位符用来描述字符串或单词的边界, “^” 和 “$” 分别指定字符串的开始和结束,“\b” 描述单词的前边界或后边界, “\B” 表示非单词的边界。以下是正则表达式的定位符:

字符描述
^匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。

1.6 分组构造

分组构造描述了正则表达式的子表达式,用于捕获输入字符串的子字符串。

1.7 匹配模式

匹配模式是指匹配的时候使用的规则。以下几种常见的匹配模式:

  • 不区分大小写模式:指定单词匹配时正则表达式不区分字符串中的大小写。
  • 单行模式(或者叫点号通配):改变元字符的匹配方式,用于匹配任意字符。
  • 多行模式:改变 “^” 和 “$” 的匹配模式,匹配字符串中一部分。

2. re模块

2.1 re模块介绍

Python标准库中提供了与Perl类似的正则表达式匹配操作re模块。

正则表达式中的许多元字符使用 “\” 开头,这与Python在字符串中相同的字符使用冲突。极端情况下,想要匹配 文字”\“ ,正则表达式需要写成 ”\\\\“ 。

有一种常见的解决方案是在定义字符串时使用 “r” 前缀。例如:

print('\\\\') # 输出\\
print('\\') # 输出\
print(r'\\') # 输出\\
print('\n') # 输出一个换行符
print(r'\n') # 输出\n字符

2.2 compile函数

compile函数用于编译正则表达式,生成Pattern对象。后面可以使用这个Pattern对象进行正则匹配

import re

pattern = re.compile(r'\w+')

2.3 match函数

match方法用于查找字符串指定位置正则匹配。它只匹配一次,而不会匹配所有的结果。

import re

pattern = re.compile(r'\d+')                    # 匹配至少一个数字
m1 = pattern.match('one123')                    # 默认匹配整个字符串
print(m1)                                       # None
m2 = pattern.match('one123', 3, 5)              # 匹配位置从3到5的字符
print(m2)                                       # 匹配到则返回Match对象
print(m2.group())                               # 12

m3 = re.match(r'\d+', 'one123')                 # 不使用compile直接匹配,匹配整个字符串
print(m3)                                       # None
 
m4 = re.match(r'[a-z]+', 'Abcde', re.I)         # 使用忽略大小写模式
print(m4)                                       # 返回Match对象
print(m4.group())                               # Abcde

2.4 re.search

search方法用于查找字符串指定位置正则匹配。它只匹配一次,不返回所有结果。

search函数与match的区别:match函数需要完全满足正则表达式才返回,而search函数只需要字符串中包含匹配正则表达式的子串就认为匹配。

import re

pattern = re.compile(r'\d+')                    # 匹配至少一个数字
m1 = pattern.search('one123')                   # 默认匹配整个字符串
print(m1)                                       # 123
m2 = pattern.search('one123', 3, 5)             # 匹配位置从3到5的字符
print(m2)                                       # 匹配到则返回Match对象
print(m2.group())                               # 12

m3 = re.search(r'\d+', 'one123')                # 不使用compile直接匹配,匹配整个字符串
print(m3)                                       
print(m3.group())                               # 123
 
m4 = re.search(r'[a-z]+', 'Abcde', re.I)        # 使用忽略大小写模式
print(m4)                                       # 返回Match对象
print(m4.group())                               # Abcde

2.5 re.findall

match和serach函数都是只匹配一次。在很多情况下,会需要搜索整个字符串,获取全部匹配结果,可以使用findall函数。

findall函数使用方法与match和search类似,但返回结果不同:无论是否匹配到都会返回一个list对象。

import re

pattern = re.compile(r'\d{2}')                  # 匹配2个数字
m1 = pattern.findall('one1234')                 # 默认匹配整个字符串
print(m1)                                       # ['12', '34']

m2 = pattern.findall('one123', 0, 4)            # 匹配位置从3到5的字符
print(m2)                                       # []

m3 = re.findall(r'\d+', 'one123')               # 不使用compile直接匹配,匹配整个字符串
print(m3)                                       # ['123']

m4 = re.findall(r'[a-z]', '123Abcde', re.I)     # 使用忽略大小写模式
print(m4)                                       # ['A', 'b', 'c', 'd', 'e']

2.6 re.split

字符串也有split方法,但是字符串只能完全匹配相同的字符。re模块中的split方法可以用正则表达式丰富分隔字符串的规则。

import re

pattern = re.compile(r'[\s\, \;]+');        # 匹配空格,和;
m1 = pattern.split('a,b;; c   d')
print(m1)                                   # ['a', 'b', 'c', 'd']#

m2=re.split(r'[\s\,\;]+', 'a,b;; c   d')
print(m2)                                   # ['a', 'b', 'c', 'd']#

2.7 re.sub

同样,re模块也提供了使用正则表达式来替换字符串的方法————sub方法

import re

s = 'hello 123 world 456'
pattern = re.compile(r'(\w+) (\w+)')
m1 = pattern.sub('hello world', s)  # 使用 'hello world' 替换 'hello 123' 和 'world 456'
print(m1)                           # hello world hello world

m2 = pattern.sub('hello world', s, 1)  # 只替换一次
print(m2)                           # hello world world 456

m3 = re.sub(r'(\w+) (\w+)', 'hello world', s, 1)
print(m3)                           # hello world world 456

3. 扩展知识

3.1 re的分组分配

正则表达式可以分组,不同的组使用一对圆括号 “()” 来隔离。

import re

p1 = re.compile('\d-\d-\d')          # 不分组
m1 = p1.match('1-2-3')
print(m1.groups())                   # ()
print(m1.group())                    # 1-2-3

p2 = re.compile('(\d)-(\d)-(\d)')    # 分组
m2 = p2.match('1-2-3')
print(m2.groups())                   # ('1', '2', '3')
print(m2.group())                    # 1-2-3

m3 = re.findall('(\d)-(\d)-(\d)', '1-2-3-4-5-6')
print(m3)                            # [('1', '2', '3'), ('4', '5', '6')]

3.2 贪婪与非贪婪匹配

贪婪与非贪婪模式指的是限定符操作时尽可能多地匹配字符串还是尽可能少地匹配字符串。

贪婪匹配指的是限定符尽可能多地匹配字符串。默认情况下限定符都是贪婪匹配。

非贪婪匹配指的则是限定符尽可能少地匹配字符串。在限定符后加上 “?” 表示非贪婪匹配。

import re

m1 = re.match(r'.+', 'Are you ok? No, I amd not ok.') # 贪婪
print(m1.group())                  # Are you ok? No, I amd not ok.

m2 = re.match(r'.+?', 'Are you ok? No, I amd not ok.') # 非贪婪
print(m2.group())                 # A

m3 = re.findall(r'<.+>', r'<this><is><an><example>')   # 贪婪
print(m3)                         # ['<this><is><an><example>']

m4 = re.findall(r'<.+?>', r'<this><is><an><example>')   # 非贪婪
print(m4)                         # ['<this>', '<is>', '<an>', '<example>']

3.3 零宽断言

零宽断言正如它的名字一样,是一种零宽度的匹配,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已。

字符描述
(?=exp)匹配exp后面的位置
(?<=exp)匹配exp前面的位置
(?!exp)匹配后面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置
import re

s = r'eating apple seeing paper watching movie'
m1 = re.findall(r'(\b\w+?)ing', s)
print(m1)                  # ['eat', 'see', 'watch']

m2 = re.findall(r'(.+?)(?=ing)', s)
print(m2)                  # ['eat', 'ing apple see', 'ing paper watch']

m3 = re.findall(r'(.+?)(?<=ing)', s)
print(m3)                  # ['eating', ' apple seeing', ' paper watching']

s = 'unite one unethical ethics use unite ultimate'
m4 = re.findall(r'\b(?!un)\w+\b', s)
print(m4)                  # ['one', 'ethics', 'use', 'ultimate']

m5 = re.findall(r'(?<![a-z])\d{3,}', 'abc123, 123, 4567')
print(m5)                  # ['123', '4567']
0

评论区