Python中的正则表达式

一、 概述

1.1什么是正则表达式?

正则表达式(Regular Expression) 是一种文本模式,包括普通字符和特殊字符,特殊字符又称为元字符。代码中有regexp或regex或RE这几种说法。正则表达式用单个字符串,来描述或匹配某个字符串的句法规则。一般用来查找文本。

就是通过普通字符+特殊字符组成一个字符串,然后按照特定的语法描述一个规则,通过这个规则来查询对应的字符串。

1.2 正则表达式的基础语法

^ 代表字符串的开始位置
$ 匹配输入字符串的结束位置
+ 匹配一个或者多个,表示前面的字符至少出现一次(1次或者多次)
* 匹配的字符出现0次或者多次
? 前面的字符最多只能出现一次(0次或者1次)

1.3普通字符的匹配

二、 一些常用的规则的描述

2.1手机号

如 : 132-9527-2048,首先分析对象的描述和结构。
第一位:1 是固定的,都是1
第二位:只能是一些特定的数字 [34578]
第三位到第十一位共9位:可以为任意的数值

^1[34578][0-9]{9}$

图上所示,以1开始,[34578]中的任意一个,[0-9]中的任意数字重复9次(1个+8次重复),其中[0-9]也可以用\d 来代替,和转义字符类似,称之为元字符。

2.2文本的匹配

如有一个句子: “This is a book”,想要从中查询“is”这个单词。

\bis\b

可以通过notepad++来验证正则表达式

2.3 HTTP地址的匹配

一般的http的地址为: http://www.baidu.com
其中http://www.是固定的字符,结尾的.com也是固定,中间是任意字符。其中需要注意的就是元字符要用反斜杠转义。

http[s]?:\/\/www\.+.+\.com

如果有可能是https的化,那可以如下:
http[s]?😕/www.+.+.com

2.4 日期

日期的格式为:2019/1/17或者2019-01-17,可以使用如下的正则表达式:
[0-9]{4}[/-][0-9]{2}[/-][0-9]{2},要求月份和日期必须要是用两位数字来表示,其中[0-9]可以用定义好的\d来表示,所以为:
\d{4}[/-]\d{2}[/-]\d{2}

3times 由{4} 生效表示重复4次,开始的时候有疑问,为什么不是 4times 。后来明白作者的用意,正则表达式是一个规则,用这个规则去从字符串开始匹配到结束(注意计算机读字符串可是不会分行的,都是一个串,我们看到的多行,人家会认为是个 \t )这里设计好像小火车的轨道一直开到末尾。digit 传过一次,3times表示再来三次循环,共4次,后面的once同理。

可以对正则进行一个分组,分组的作用后面再补充:
(\d{4})/-/-

三、python中正则表达式的使用

python中存在re模块,具有所有正则表达式的所有功能。

3.1 python中正则表达式查询函数

3.1.1 re.match

语法规则:

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

match方法是从目标字符串的开头开始匹配一个模式,如果有内容但是不是起始位置匹配成功,就会返回none
只能匹配开始是要匹配的字符,否者返回None,有点像正则中匹配开头的字符^
1、"2022年2月12日",此时,使用re.match()方法,可以正确匹配到20
2、"今天是2022年2月12日",此时用re.match()方法,无法匹配(必须是2022开头的字符串)

import re
s = 'Hey,he is a boy'
print('match:',re.match(r'\bis\b', 'He is a boy'))
print('match:',re.match(r'\bis\b', 'is a boy?'))

返回值:

match: None  //虽然有匹配项,但是不是第一个返回None
match: <re.Match object; span=(0, 2), match='is'>  //第一个,返回是所在的位置和长度,match返回匹配的内容

3.1.2 正则表达式中的分组和匹配

3.1.2.1 分组的语法

2019-01-18或者2019/01/18,正则表达式分组为:(\d{4})/-/-

可以通过 re.match(pattern,string).group(num=0) 的方法来获取到各个分组,其中num为0-任意数字,为0的时候表示是所有。

3.1.2.2 分组的例子

如下的例子表示从日期中将分隔符号转为年月日,将2019-01-18转换成2019年01月18日。

import re
match_obj = re.match(r'(\d{4})[/-](\d{2})[/-](\d{2})','2019-01-18')
g0 = match_obj.group(0)
g1 = match_obj.group(1)
g2 = match_obj.group(2)
g3 = match_obj.group(3)
# g0表示全部,g1表示第一个分组,以此类推
print(f'g0:{g0}\ng1:{g1}\ng2:{g2}\ng3:{g3}')
s1 = '{}年{}月{}日'.format(g1,g2,g3)
print(s1)
# 使用groups返回tuple
print(f'groups:{match_obj.groups()}')

运行结果:

g0:2019-01-18
g1:2019
g2:01
g3:18
2019年01月18日
groups:('2019', '01', '18')

所以在正则表达式进行分组,是为了后面能够提取出各个分组中的内容,
这个例子看起来用字符串中的replace方法或者也合适,但是要在众多的字符串中查找到符合日期类型的字符串,还是用正则表达式更合适,如下用的是search()。

4545df45ads456f546as45f45as4d5fa546f45a54f54ad45fasdfa
fasd4f56a54sdf445asfadfjaiosdfioaijfjiasjidfijjifjaisjdfasdfaf54sad5f6a4dfa54sdfas65d54f4.>56asd564f54ad456ffjaodifajsdfoasdfasdf1as3df53as5df15as123f12312f123a213sdf132a2>13sdffasd4f56a54sdf445asfadfjaiosdfioaijfjiasjidfijjifjaisjdfasdfaf54sad5f6a4dfa54sdfas65>>d54f4>56asd564f54ad456ffjaodifajsdfoasdfasdf1as3df53as5df15as12asdfasdf3f12312f123>a213sdf132a213sdff45a654df5a6sdfadfjasidfaoojidfajidfijajidfadsfafad1121315412019->02-06545456456456asdjfiaiosdjfoiadsjifajisdjifoaad4fa65df4a4d

with open(r'd:\Python\Test1034.txt','r') as f_txt:
    for f in f_txt:
        res = re.search(r'(\d{4})[/-](\d{2})[/-](\d{2})',f)
        if res:
            print(res.group())

返回值:2019-02-07

3.1.2.3 使用groups()来返回所有的分组的元组

通过如下的例子可以看出使用group()和使用groups()的差别,group返回了整个匹配的字符串,而groups返回的是正则分组捕获的内容的tuple

match_obj = re.search(r'(\d{4})[/-](\d{2})[/-](\d{2})','Today is 2019/01/18,yesterday is 2019-01-17')
print(match_obj.groups())
print(match_obj.group())
返回值:
('2019', '01', '18')
2019/01/18
import re
with open(r'user_info_file.txt','r') as f:
    str1 = str(f.readlines())
    print(type(str1),str1)
    res=re.search(r'(\d{4})\-\>(\d{2})\-(\d{2})',str1)
    print(res.group())
    print(res.group(1))
    print(res.group(2))
    print(res.group(3))


    print(res.groups())
    print(res.groups()[0])
    print(res.groups()[1])
    print(res.groups()[2])
运行的结果:
/Users/yangfan/Documents/pythonProject1/bin/python /Users/yangfan/Doc/pythonProject1/t2.py
<class 'str'> ['4545df45ads456f546as45f45as4d5fa546f45a54f54ad45fasdfafasd4f56a54sdf445asfadfjaiosdfio2022->07-02aijfjiasjidfijjifjaisjdfasdfaf54sad5f6a4dfa54sdfas65d54f4.>56asd564f54ad456ffjaodifajsdfoasdfasdf1as3df53as5df15as123f12312f123a213sdf132a2>13sdffasd4f56a54sdf445asfadfjaiosdfioaijfjiasjidfijjifjaisjdfasdfaf54sad5f6a4dfa54sdfas65>>d54f4>56asd564f54ad456ffjaodifajsdfoasdfasdf1as3df53as5df15as12asdfasdf3f12312f123>a213sdf132a213sdff45a654df5a6sdfadfjasidfaoojidfajidfijajidfadsfafad1121315412019->02-06545456456456asdjfiaiosdjfoiadsjifajisdjifoaad4fa65df4a4d']
2022->07-02
2022
07
02
('2022', '07', '02')
2022
07
02

3.1.1 re.search

和match函数类似,search所查找的内容是整个字符串的,并且返回第一个匹配的对象。
返回的是第一个匹配的对象,不是返回所有的匹配的对象。

match_obj = re.search(r'(\d{4})[/-](\d{2})[/-](\d{2})','Today is 2019/01/18,yesterday is 2019-01-17')
print(match_obj)

re.match和re.search的区别:re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

3.1.2python中正则表达式检索和替换

上面提供了正则表达式的查找和检索,后面提供的是替换。应该包括4个部分:正则表达式、要替换的字符串、替换后的内容、替换多少个,sub是substitute表示替换。

re.sub(pattern, repl, string, count=0, flags=0)
参数:
pattern : 正则中的模式字符串。
repl : 替换的字符串,也可为一个函数。
string : 要被查找替换的原始字符串。
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。

sub_obj = re.sub(r'\btall\b', 'low','She is young,he is tall,tall and handsome',1)
print(sub_obj)
返回值:
She is young,he is low,tall and handsome

如果将其中的count改为2,那就会进行2次替换。
返回值:
She is young,he is low,low and handsome
例子2:

import re
s = '今天的天气是晴天,明天的天气也是晴天,后天的天气是多云转晴天'

# 使用re模块,替换s中的晴天的字符串,设置count=1
s1 = re.sub(pattern='晴天',repl='多云',string=s,count=1)
print('s1:',s1)

# 使用re模块,替换s中的晴天的字符串,设置count=1
s2 = re.sub('晴天','阴天',s,count=3)
print('s2:',s2)

3.1.2.1 参数中repl是一个函数的情况

例如将一个字符串中的所有的数字都翻倍:

def double_digtal(dig):
    return str(int(dig.group())*2)

s = 'A564ASDF4ASD4F57835A4FA4DF5A54'
# 使用正则表达式将单个数字进行翻倍
sub_obj = re.sub('\\d',double_digtal,s)
print(sub_obj)
返回值:A10128ASDF8ASD8F101416610A8FA8DF10A108

延申的一个问题,将连续的数字进行翻倍,而不是对单个数字进行翻倍。
主要是正则表达式的写法问题,使用\d+开启贪婪,可以查找到尽量多的内容。

# 正则表达式开启贪婪模式
s = 'a460df4000000000005a1545455461d4a56fa1a23d'
lst_res = re.findall(r'\d+',s)
print(lst_res)
结果:
['460', '4000000000005', '1545455461', '4', '56', '1', '23']

# 正则表达式关闭贪婪模式
结果:
['4', '6', '0', '4', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '5', '1', '5', '4', '5', '4', '5', '5', '4', '6', '1', '4', '5', '6', '1', '2', '3']
如果使用?关闭贪婪模式,和使用\d的效果是一样的

例子:

import re
def dashrepl(matchobj):
    print(f'matchobj:{matchobj}')
    print(f'match.group(0):{matchobj.group(0)}')
    if matchobj.group(0) == '-': return ' '
    else: return '*'

res = re.sub('-{1,2}', dashrepl, 'pro----gram-files')
print(f'res:{res}')

# 运行的结果 强两次次匹配到--,然后替换成* ,最后一次匹配到-,替换成空格。
matchobj:<re.Match object; span=(3, 5), match='--'>
match.group(0):--
matchobj:<re.Match object; span=(5, 7), match='--'>
match.group(0):--
matchobj:<re.Match object; span=(11, 12), match='-'>
match.group(0):-
res:pro**gram files

3.1.2.2 re.compile

在match和search以及sub中,都会使用正则表达式,也可以将正则表达式事先编译好作为一个对象,然后再使用,这样的好书
语法:
re.compile(pattern[, flags])
pattern: 字符串形式的正则表达式
flages 字段的内容如下:

re.I 忽略大小写
re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
re.M 多行模式
re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
re.X 为了增加可读性,忽略空格和 # 后面的注释

如在一个字符串中查找数字:

s = 'a46df45a151d4a56fa1a23d'
compile_sub = re.compile(r'\d')
# 这里等价于match.(r'\d',s)
res = compile_sub.match(s,1) # 返回的是match的对象
print(res)
print(res.group())
print(res.span())
返回值:
<re.Match object; span=(1, 2), match='4'>
4
(1, 2)

3.1.2.3 findall

在字符串中找到正则表达式匹配的所有的字符串,返回一个list,没有找到就返回空列表。和match和search的区别比较明显,match和search是匹配一次,而findall是匹配所有。

findall(string[, pos[, endpos]])
如果有需要,可以指定查找的开始和结束的位置

s = 'a46df45a151d4a56fa1a23d'
lst_res = re.findall(r'\d',s)
print(lst_res)
结果:['4', '6', '4', '5', '1', '5', '1', '4', '5', '6', '1', '2', '3']
达到了找出字符串中所有数字的目的

当然也可以通过string的方法进行数字的查找

list_res = []
for i in s:
    if i.isdigit():
        list_res.append(i)

print(list_res)
返回值:['4', '6', '4', '5', '1', '5', '1', '4', '5', '6', '1', '2', '3']

3.1.2.4 finditer 查找所有匹配,以迭代器返回

用法和findall相同,返回的是match对象的迭代器(地址),而更不是地址,这样在大量返回值的时候会更加节约内存开销。

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

s = 'a46df45a151d4a56fa1a23d'
iter_f = re.finditer(r'\d',s)
print(iter_f.__next__()) //迭代器的__next__()方法

3.1.2.5 split

将字符串分隔后,返回列表
re.split(pattern, string[, maxsplit=0, flags=0])

pattern 匹配的正则表达式
string 要匹配的字符串。
maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。>参见:正则表达式修饰符 - 可选标志

例子:使用成绩这个字符串进行分割。

import re

s='134语文成绩148数学成绩139英语成绩200其他成绩'
res = re.split('成绩',s)
print(res)
['134语文', '148数学', '139英语', '200其他', '']
posted on 2022-07-04 11:55  飞飞fly  阅读(1405)  评论(0)    收藏  举报