正则表达式

前言

汗!!!记性太差,由于正则用的不是很多,偶尔需要用时总要重新去查,索性重新整理一波正则基础用法做下巩固。

正则表达式是一组由字母和符号组成的特殊文本,可以帮助我们从某个复杂的字符串中,提取出满足我们要求的特殊文本。

用一个形象的比喻大致体会一下正则匹配的过程。苹果相当于写的“正则表达式”,字符串相当于“水果市场”,“正则匹配的过程”就相当于拿着苹果去“水果市场”找苹果的过程,每找到一个就返回一个,否则就什么也没有

re库

在Python中,re库又叫做正则表达式库,也属于Python标准库之一,不用安装就可以使用。

在线测试地址

常用操作符介绍

正则表达式之所以这么强大,是因为拥有这么多专用操作符。为了方便大家记忆,我特意将操作符分为三类:

  • 第一,元字符;
  • 第二,量化符;
  • 第三,特殊符;

1. 常用元字符

所谓“元字符”,指的是那些不仅仅可以表示字符本身含义、并且还可以表示其他特殊含义的字符。

常用的元字符有. [ ] () ^ $ | \ ? * + { }共11种,为了更清楚地说明每个元字符的含义

元字符 含义
. 点号,匹配换行符(\n)以外的任何单个字符
[] 方括号,匹配括号内的任意字符,括号中的每个字符是 or 的含义
() 组,匹配括号内的表达式,括号中的每个字符是 and 的含义
^ 乘方符号,表示以 ... 开头(在方括号中使用 ^ , 表示 方括号里面的字符集合)
$ 美元符号,表示以 ... 结尾
| 或运算符,用于匹配符号前或符号后的字符
\ 转义字符,可以让某些字母表示特殊含义

2. 常用量化符

“量化符”指的就是将紧挨着量化符前面的那个字符,匹配0次、1次或者多次。

量化符号 含义 数学表达式
? 匹配前面紧挨的字符,最多匹配1次,即要么有一次,要么没有 0≤x≤1
* 匹配前面紧挨的字符,匹配0次或多次 x≥0
+ 匹配前面紧挨的字符,匹配1次或多次,即至少有一次 x≥1
匹配前面紧挨的字符,正好匹配n次 x=n
匹配前面紧挨的字符n次,至少匹配n次 x≥n
匹配前面紧挨的字符n到m次,即至少n次,至多m次 n≤x≤m

3. 常用特殊符号

“特殊符”,指的就是由转义字符加某些字母组合而成的具有特殊意义的特殊字符,下面列举一部分:

注:如果在方括号中使用 ^ , 表示 方括号里面的字符集合

特殊符号 描述
\d 匹配一个数字字符,等价于[0-9]
\D 匹配一个非数字字符[^0-9]
\s 匹配任意空白字符,包括空格、制表符换页符等,等价于[\f\n\r\t\v]
\S 匹配任意非空白字符[^\f\n\r\t\v]
\w 匹配任意一个文字字符,包括大小写字母、数字、下划线,等价于[A-Za-z0-9]
\W 匹配任意一个非文字字符[^A-Za-z0-9]
\b 单词边界
\B 非单词边界
(?=...) 匹配 ... 出现在之后的位置
(?!=...) 匹配 ... 不出现在之后的位置
(?<=...) 匹配 ... 出现在之前的位置
(?<!...) 匹配 ...不出现在之前的位置
(?#...) 注释
# 例子:
test = "身高:188,体重:140,学号:1401,密码:857"
print(re.findall(r'(?<=密码.)(?#必须在密码:之后)\d+',test))

# 返回结果是:['857']

4. 贪婪模式和非贪婪模式

举个例子:

#把下面的字符串中的所有html标签都提取出来
source = '<html><head><title>Title</title>'
#得到这样的一个列表
['<html>', '<head>', '<title>', '</title>']
# 写出如下代码
import re
p = re.compile(r'<.*>')
print(p.findall(source))

# 但是运行结果,却是:
['<html><head><title>Title</title>']

这样并没有得到想要的列表

解析: 在正则表达式中, ‘*’, ‘+’, ‘?’ 都是贪婪地,使用他们时,会尽可能多的匹配内容,

所以, <.*> 中的 星号(表示任意次数的重复),一直匹配到了 字符串最后的 </title> 里面的e。

解决这个问题,就需要使用非贪婪模式,也就是在星号后面加上 ? ,变成这样 <.*?>

代码改为:

import re
# 注意多出的问号
p = re.compile(r'<.*?>')
print(p.findall(source))

# 运行结果:
['<html>', '<head>', '<title>', '</title>']

常用方法介绍

在re库中,一共提供了三个函数用于查找匹配,分别是match()search()还有findall()

1. 三大函数对比

在这三个函数中,用的最多的是findall()这个函数,其次是search()函数,match()函数则用的很少。下面我们一一说明它的含义:

  • match(pattern,string):匹配字符串的开头,如果开头匹配不上,则返回None;
  • seach(pattern,string):扫描整个字符串,匹配后立即返回,不在往后面匹配;
  • findll(pattern,string):扫描整个字符串,以列表形式返回所有的匹配值;

其中,pattern表示用于匹配的正则表达式,string表示待匹配的字符串。

2. 三大函数用法对比

首先,导入相关库

import re

match() 函数

s = "黄同学喜欢装杯,喜欢当卷崽"
s1 = "喜欢吹牛,喜欢划水"
re.match("喜欢",s)

# re.match("喜欢",s) == None
# 由于match()只匹配开头,如果开头不是“喜欢”二字,那么就返回None。
re.match("喜欢",s1)
# 返回结果是:<re.Match object; span=(0, 2), match='喜欢'>
# 上述结果得到的是一个匹配对象,如果想要获取其中的匹配值,就必须调用匹配对象的group()方法,获取具体的匹配值
re.match("喜欢",s1).group()
# 返回结果是:’喜欢’

search() 函数

re.search("喜欢",s)
# 返回结果是:<re.Match object; span=(3, 5), match='喜欢'>
# 上述结果同样返回的是一个匹配对象,仍然需要调用group()方法,获取到具体的匹配值
re.search("喜欢",s).group()
# 返回结果是:'喜欢'

findall() 函数

re.findall("喜欢",s)
# 返回结果是:['喜欢', '喜欢']

其它常用函数

除了上述三个用于查找匹配的函数之外,还有用于切分的split()函数,有用于替换的sub()函数。另外还有两个常用修饰符re.Ire.S

  • split(pattern,string):按照某个匹配的正则表达式,将整个字符串切分后,以列表返回;
  • sub(pattern,repl,string):按照某个匹配的正则表达式,将整个字符串的某个字串替换为另外一个字串;
  • re.I:让正则表达式自动忽略大小写;
  • re.S:让“.”能够匹配包括换行符在内的任意字符;
  • compile:把正则表达式编译成一个对象,方便后面使用;

pattern表示用于匹配的正则表达式,string表示待匹配的字符串,repl表示替换后的字串。

split() 分割

将字符串按照数字切分,以一个汉字组成的列表返回。

\d是匹配某一个数字,若字符串里面,有2位的数字还有3位的数字,可以使用\d+表示匹配>=1个数字

s = "我1爱22我333的4444祖55国6"
print(re.split('\d+',s))

# 返回结果是:['我', '爱', '我', '的', '祖', '国', '']

sub() 替换

匹配模式替换

# 将后缀为3位数字的邮箱后缀改为qq;
mil = '3177126996@126.com'
result = re.sub('@\d{3}','@qq',mil)
print(result)

# 返回结果:3177126996@qq.com

指定替换函数

也可以指定更加复杂的替换,可以把 sub的第2个参数 指定为一个函数 ,该函数的返回值,就是用来替换的字符串

import re

names = '''

<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律

<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式

<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''

# 替换函数,参数是 Match对象
def subFunc(match):
    # Match对象 的 group(0) 返回的是整个匹配上的字符串, 
    src = match.group(0)
    
    # Match对象 的 group(1) 返回的是第一个group分组的内容
    number = int(match.group(1)) + 6
    dest = f'/av{number}/'

    print(f'{src} 替换为 {dest}')

    # 返回值就是最终替换的字符串
    return dest

newStr = re.sub(r'/av(\d+?)/', subFunc , names)
print(newStr)

获取组内字符串,如下

match.group(0) # 获取整个匹配字符串
match.group(1) # 获取第1个组内字符串
match.group(2) # 获取第2个组内字符串

Python 3.6 以后的版本 ,写法也可以更加简洁,直接像列表一样使用下标,如下

match[0]
match[1]
match[2]

上面这个例子中:

正则表达式 re.sub 函数执行时, 每发现一个 匹配的子串, 就会:

  • 实例化一个 match对象

    这个match 对象包含了这次匹配的信息, 比如:整个字符串是什么,匹配部分字符串是什么,里面的各个group分组 字符串是什么

  • 调用执行 sub函数的第2个参数对象,也就是调用回调函数subFunc

    并且把刚才产生的 match 对象作为参数传递给 subFunc

re.I()

将字符串中的h匹配出来,不区分大小写;

s = 'Hello呀hello'
re.findall("h",s1)

# 返回结果是:['h']

如果加了re.I,系统会忽略大小写,那么h和H就是同一个字符。

re.findall("h",s,re.I)

# 返回结果是:['H', 'h']

re.s()

将字符串整个匹配出来,包括换行符;

如果不加re.S,“.”不能匹配“\n”换行符。

s2 = "黄\n同学"
re.findall(".+",s)

# 返回结果是:['黄', '同学']

如果加了re.S,“.”此时能够匹配“\n”换行符

re.findall(".+",s,re.S)
# 返回结果是:['黄\n同学']

compile()

当规则比较长时或者其它地方也要用到该规则时比较方便,下面只是展示用法

res = re.compile('\d')
result = re.findall(res,'1hello9')
# 或者这样写
result1 = res.findall('1hello9')
print(result)

# 返回结果都为:['1', '9']

re.A()

\w 缺省情况也包括 Unicode文字字符,如果指定 ASCII 码标记,则只包括ASCII字母

s = '''
黄某某
tony
张某某
'''

p = re.compile(r'\w{2,4}')
print(p.findall(s))

# 结果为:
['黄某某','tony','张某某']

# 如果只要英文的字符 (ASCII 码)
p = re.compile(r'\w{2,4}',re.A)
print(p.findall(s))
# 结果为:
['tony']

re.M()

缺省情况是单行模式,多行模式需要加上re.M

content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80
'''
p = re.compile(r'^\d+',re.M)
for i in p.findall(content):
    print(i)
    
# 结果为:
> 001
> 002
> 003

# 如果去掉 compile 的第二个参数 re.M, 运行结果如下
> 001

posted @ 2021-10-10 14:09  者諹  阅读(114)  评论(0编辑  收藏  举报