Python之路(第二十一篇) re模块

时间:2021-08-06 03:19:03

一、re模块

正则表达式本身是一种小型的、高度专业化的编程语言,正则表达式就是字符串的匹配规则,在多数编程语言里都有相应的支持,python里对应的模块是re,正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。

匹配语法

re方法(匹配模式,字符串,flag)

(一)字符

1、元字符

普通字符 匹配内容
. 匹配任意除换行符"\n"外的字符,若指定flag DOTALL,则匹配任意字符,包括换行
\ 转义字符,使后一个字符改变原来的意思
* 匹配前一个字符0或多次
+ 匹配前一个字符1次或无限次
? 匹配一个字符0次或1次, 和防止贪婪匹配
^ 匹配字符串开头。在多行模式中匹配每一行的开头,^元字符如果写到[]字符集里就是反取
$ 匹配字符串末尾,在多行模式中匹配每一行的末尾
| 或。匹配|左右表达式任意一个,从左到右匹配,如果|没有包括在()中,则它的范围是整个正则表达式
{} {m}匹配前一个字符m次,{m,n}匹配前一个字符m至n次,若省略n,则匹配m至无限次
[] 字符集。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]或[a-c]。abc表示取反,即非abc。所有特殊字符在字符集中都失去其原有的特殊含义。用\反斜杠转义恢复特殊字符的特殊含义。
() 被括起来的表达式将作为分组,从表达式左边开始没遇到一个分组的左括号“(”,编号+1.分组表达式作为一个整体,可以后接数量词。表达式中的|仅在该组中有效。 匹配任何在圆括号内的正则表达式, 并表明分组的开始和结束; 分组的内容在完成匹配后可以提取出来,而且可以在后面的字符串中用特殊的number序列匹配

量词

量词 用法说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

元字符匹配次数说明

Python之路(第二十一篇)  re模块

Python之路(第二十一篇)  re模块

例子

  import re

# '.'字符 , 从开头一直找 ,返回找到的所有字符
print(re.findall("nichol..","nicknicholas")) #['nicholas']

#'^'匹配字符串开头。在多行模式中匹配每一行的开头
print(re.findall("^nick","nickcalnicholas")) #['nick']

#'$'匹配字符串末尾,在多行模式中匹配每一行的末尾
print(re.findall("ick$","nicholasnicknick")) #['ick']

# '*' 匹配*号前的字符0次或多次
print(re.findall("a*","abcabaad")) #['a', '', '', 'a', '', 'aa', '', '']

# '+' 匹配前一个字符1次或多次
print(re.findall("a+","abcabaad")) #['a', 'a', 'aa']

# '?' 匹配前一个字符1次或0次
print(re.findall("a?","abcabaad")) #['a', '', '', 'a', '', 'a', 'a', '', '']

# '|' 或。匹配|左右表达式任意一个,从左到右匹配,如果|没有包括在()中,则它的范围是整个正则表达式
print(re.findall("(ab|AC)","abcadxyzab")) #['ab', 'ab']
print(re.findall("(ab|AC)","ACxyznick")) #['AC']

# '{m}'匹配前一个字符m次,{m,n}匹配前一个字符m至n次,若省略n,则匹配m至无限次
print(re.findall("a{2,4}","abcaadaxyzaaaa")) #['aa', 'aaaa']

# '[]'字符集。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]或[a-c]。
print(re.findall("[ac]","acxyzabc")) #['a', 'c', 'a', 'c']
print(re.findall("[^ac]","acxyzabc")) #['x', 'y', 'z', 'b']
print(re.findall('a[bc]d','acd')) #['acd']
print(re.findall('[a-z]','acd')) #['a', 'c', 'd']
print(re.findall('[1-9]','45dha3')) #['4', '5', '3']
print(re.findall('[^ab]','45bdha3')) #['4', '5', 'd', 'h', '3']
print(re.findall('[\d]','45bdha3')) #['4', '5', '3']

# '()'匹配被括起来的内容
print(re.findall("(ab)",'xyz453d6gab89uzaxby')) #['ab']
print(re.findall("(ab)",'xyz453d6gab89uzaby')) #['ab', 'ab']
print(re.findall(r'(ad)+', 'addhahdwha2e3adadad')) #['ad', 'ad']

  

2、预定义字符集

预定义字符 匹配说明
\d 数字:[0-9] 字符英文说明digit
\D 等同于非数字:[^0-9]
\s 匹配任意的空白符:[<空格>\t\r\n\f\v] 可以匹配空格、换行符、缩进符号等 . 字符英文说明space
\S 非空白字符:[^\s]
\w 匹配包括下划线在内的任何字字符:[A-Za-z0-9_](大小写的A-Z,数字0-9) 字符英文说明word
\W 匹配非字母字符,即匹配特殊字符
\A 仅匹配字符串开头,同^
\Z 仅匹配字符串结尾,同$
\b 匹配\w和\W之间,即匹配单词边界匹配一个单词边界,也就是指单词和空格间的位置。例如, 'lo\b' 可以匹配"hello" 中的 'lo',但不能匹配 "hellohi" 中的 'lo'。
\B [^\b]匹配所有的非边界字符

例子

  import re

# 匹配数字:[0-9]
print(re.findall("\d","你好我11大22,求十大3")) #['1', '1', '2', '2', '3']

#匹配非数字[0-9]
print(re.findall("\D","你好我11大22,求十大3")) #['你', '好', '我', '大', ',', '求', '十', '大']

# 匹配任意的空白符(打印不出来的)
print(re.findall("\s","你好我好\n,大家好\t")) #['\n', '\t']

# 匹配非空白字符:[^\s]
print(re.findall("\S","你好我好\n,大家好\t")) #['你', '好', '我', '好', ',', '大', '家', '好']

# 匹配包括下划线在内的任何字字符:[A-Za-z0-9_](大小写的A-Z,数字0-9),python中支持中文
print(re.findall("\w","ni好2_,大家好,HI\n")) #['n', 'i', '好', '2', '_', '大', '家', '好', 'H', 'I']

# 仅匹配字符串开头,同^
print(re.findall("\Ah","hell hello")) #['h']

# 仅匹配字符串结尾,同$
print(re.findall("o\Z","hellhello")) #['o']

# \b匹配\w和\W之间,即匹配单词边界匹配一个单词边界,也就是指单词和空格间的位置。
print(re.findall("\\b","hello word")) #['', '', '', ''] 把两个单词左右的空格边界匹配到了这里多加一个斜杠是为了转义
print(re.findall(r"\b","hello word")) #['', '', '', ''],或者不转义直接加r

  

3、字符集[]

字符集 说明
[0-9] 也可以用-表示范围,[0-9]就和[0123456789]是一个意思`
[a-z] 同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示
[A-Z] [A-Z]就表示所有的大写字母
[0-9a-fA-F] 可以匹配数字,大小写形式的a~f,用来验证十六进制字符
[A-z] 可以匹配所有的大写字母和小写字母

例子

  
  import re

print(re.findall("[0-9]+","add1ab21cc332ab")) #['1', '21', '332']
print(re.findall("[2-9]+","add1ab21cc332ab")) #['2', '332']

print(re.findall("[a-z]+","aadddccda")) #['aadddccda']
print(re.findall("[c-z]+","aadddcacda")) #['dddc', 'cd']

print(re.findall("[A-z]","A,BC,abc")) #['A', 'B', 'C', 'a', 'b', 'c']

  

4、()元字符,分组

也就是分组匹配,()里面的为一个组也可以理解成一个整体

如果()后面跟的是特殊元字符如 (adc)* , 那么*控制的前导字符就是()里的整体内容,不再是前导一个字符

例子

  
  import re
print(re.findall("(ab)+","addabccab")) #['ab', 'ab']

  

5、r原生字符

将在python里有特殊意义的字符如\b,转换成原生字符(就是去除它在python的特殊意义),不然会给正则表达式有冲突,为了避免这种冲突可以在规则前加原始字符r,或者加上转义符\

例子

  
print(re.findall("\\b","hello word"))
print(re.findall(r"\b","hello word"))

  

r是real的意思

6、注意

(1) ^有两种用法,一种是作为开头,表示匹配字符串开头。第二种是非,在字符集里用表示非

[^0-9]表示匹配非数字0-9

(2)在正则表达式中,有很多有特殊意义的是元字符,比如\d和\s等,如果要在正则中匹配正常的"\d"而不是"数字"就需要对"\"进行转义,变成'\'。

在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\d",字符串中要写成'\d',那么正则里就要写成"\\d",这样就太麻烦了。这个时候我们就用到了r'\d'这个概念,此时的正则是r'\d'就可以了。

(3)注意^和$使用所在的位置。匹配开头^要在被匹配的字符前面即“^a”,匹配结尾 $要在被匹配的字符后面即"a$"

正则 待匹配字符 匹配结果 说明
\d \d False 因为在正则表达式中\是有特殊意义的字符,所以要匹配\d本身,用表达式\d无法匹配
\d \d True 转义\之后变成\\,即可匹配
"\\d" '\d' True 如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次
r'\d' r'\d' True 在字符串之前加r,让整个字符串不转义

7、贪婪匹配与非贪婪匹配

*?, +?, ??, {m,n}? 前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配

满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配,加上?变成非贪婪匹配(惰性匹配)

正则 待匹配字符 匹配结果 说明
<.*> <script>...<script> <script>...<script> 默认为贪婪匹配模式,会匹配尽量长的字符串
<.*?> <script>...<script> <script><script> 加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串

例子

  import re

print(re.findall("<.*>","<script>...<script>")) #['<script>...<script>']
print(re.findall("<.*?>","<script>...<script>")) #['<script>', '<script>']

  

分析:<.*>是贪婪匹配,从左直接匹配到最右侧,然后向左回退到最后一个“>”,这就是回溯算法

"<.*?>"是非贪婪匹配,匹配时是从左向右匹配,遇到一个“>”马上返回,继续匹配,遇到一个遇到一个“>”马上返回。

几个常用的非贪婪匹配Pattern

  *? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

  

.*?的用法

  
  . 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。
何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在:
.*?x

就是取前面任意长度的字符,直到一个x出现

  

(二)re模块中常用功能函数

1、compile()

编译正则表达式模式,返回一个对象的模式。(可以把那些常用的正则表达式编译成正则表达式对象,这样可以提高一点效率。)

格式

re.compile(pattern,flags=0)

pattern: 编译时用的表达式字符串。

flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等。常用的flags有:

标志 含义
re.S(DOTALL) 使.匹配包括换行在内的所有字符
re.I(IGNORECASE) 使匹配对大小写不敏感
re.L(LOCALE) 做本地化识别(locale-aware)匹配,法语等
re.M(MULTILINE) 多行匹配,影响^和$
re.X(VERBOSE) 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解
re.U 根据Unicode字符集解析字符,这个标志影响\w,\W,\b,\B

补充:

  • re.S:
    • 使 . 匹配包括换行在内的所有字符
  • re.M:
    • 进行多行匹配,在新的一行中,同样可以使用^来匹配该行字符串的开头,用$来匹配该行字符串的结尾

例子

#匹配出i开头的行

string = '''fall in love with you
i love you very much
i love she
i love her'''
print(re.findall('^i.*',string,re.S))
print(re.findall('^i.*',string,re.M))

  输出结果

[]
['i love you very much', 'i love she', 'i love her']

  

例子

  
  import re

p = re.compile("\d+") #创建一个常用的规则
res = p.search("abc123sxajk")
print(res) #search直接返回的是一个match对象,需要调用group()方法获取匹配字符串
print(res.group())

  

输出结果

  
  <_sre.SRE_Match object; span=(3, 6), match='123'>
123

  

2、search()

函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

格式

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

例子

  import re

res = re.search("\d+","abc123sxajk")
print(res) #search直接返回的是一个match对象,需要调用group()方法获取匹配字符串
print(res.group())

  

输出结果

  
  <_sre.SRE_Match object; span=(3, 6), match='123'>
123

  

3、match()

同search,不过仅在字符串开始处进行匹配

格式:

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

例子

  import re

res = re.match("\d+","123sxajk")
print(res) #match匹配要从开头处匹配,match也是直接返回的是一个match对象,需要调用group()方法获取匹配字符串
print(res.group()) #如果匹配不到返回的是None,这时调用group()方法会报错

  

输出结果

  <_sre.SRE_Match object; span=(0, 3), match='123'>
123

  

分析:match()方法是从字符串的开头处匹配的,如果匹配不到则返回None,这时调用group()方法会报错,因为None没有group()方法。

*注:match和search一旦匹配成功,就是一个match object对象,而match object对象有以下方法:

  • group() 返回被 RE 匹配的字符串

  • start() 返回匹配开始的位置

  • end() 返回匹配结束的位置

  • span() 返回一个元组包含匹配 (开始,结束) 的位置

  • group() 返回re整体匹配的字符串,可以一次输入多个组号,对应组号匹配的字符串。

a. group()返回re整体匹配的字符串,a. group (n,m) 返回组号为n,m所匹配的字符串,如果组号不存在,则返回indexError异常a.groups()groups() 方法返回一个包含正则表达式中所有小组字符串的元组,从 1 到所含的小组号,通常groups()不需要参数,返回一个元组,元组中的元素就是正则表达式中定义的组。

4、findall()

返回所有满足匹配条件的结果,放在列表里

格式:

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

  
  import re

print(re.findall("a","abc sxa dda"))

  

输出结果

  
  ['a', 'a', 'a']

  

5、finditer()

搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。

格式:

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

  import re

res = re.finditer("\d","dak2fhr34ehnb567grd")
print(res) #返回的是一个可调用的迭代器
print(type(res))
print(next(res)) #通过迭代器的next()方法取出每个值,这里的每个值是一个match对象
print(next(res).group()) #通过调用match对象的group()方法来取出匹配出的值

  

输出结果

  
  <callable_iterator object at 0x02655870>
<class 'callable_iterator'>
<_sre.SRE_Match object; span=(3, 4), match='2'>
3

  

6、split()

按照能够匹配的子串将string分割后返回列表。

格式:

re.split(pattern, string[, maxsplit])

  import re

print(re.split("[ab]","abcdeg"))

  

输出结果

  ['', '', 'cdeg']

  

分析:

执行过程:首先以"a"分割字符串“abcdeg",得到结果["","bcdeg"],即一个空白字符和”bcdeg“,然后再用”b“对这个结果再次进行分割,得到['', '', 'cdeg'],即两个空白字符和‘cdeg’.

7、sub()

使用re替换string中每一个匹配的子串后返回替换后的字符串。

格式:

re.sub(pattern, repl, string, count)

例子

  
  import re

print(re.sub("a","s","hahahah"))

  

输出结果


hshshsh

  

8、subn()

替换string中匹配子串,但返回的是元组(替换的结果,替换了多少次)

格式:

subn(pattern, repl, string, count=0, flags=0)

例子

  import re

print(re.subn("a","s","hahahah"))

  

输出结果

  
  ('hshshsh', 3)

  

三、重要补充

1、findall的分组优先

如果re.findall()规则里有分组(括号),那么findall只能返回分组里的内容。

例子

  
  res = re.findall("www.(baidu|google).com","www.google.com")
print(res)

  

输出结果

  
  ['google']

  

分析:注意这里只能匹配返回出分组里的内容,而非整体的字符串。

想要整体返回需要在分组里加?:取消分组优先模式(问号在正则表达式里的第三个功能,第一个表示匹配次数,第二个是实现惰性匹配)

  
  import re

res = re.findall("www.(?:baidu|google).com","www.google.com")
print(res)

  

输出结果

  
  ['www.google.com']

  

2、 split的优先级

re.split()方法在加上分组之后可以返回被匹配分割去掉的项。在匹配部分加上()之后所切出的结果是不同的,没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项

例子

  
  import re

res1 = re.split("\d+","hello5world5hi2")
res2 = re.split("(\d+)","hello5world5hi2")
print(res1)
print(res2)

  

输出结果

  
  ['hello', 'world', 'hi', '']
['hello', '5', 'world', '5', 'hi', '2', '']