正则表达式
正则表达式是一些有字符和特殊符号组成的字符串。python 通过re模块支持正则表达式。官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
字符组
字符组:在同一位置可能出现的各种字符就组成了一个字符组。在正则表达式中用[ ] 表示。字符有很多分类:数字,字母,和标点符号等。
使用方括号的正则表达式会匹配方括号里的任何一个字符。
b[aeiu]t bat, bet, bit, but
[cr][23][dp][o2] 一个包含 4 个字符的字符串: 第一个字符是 “r” 或 “c”,后面是 “2”
或 “3”,再接下来是 “d” 或 “p”,最后是 “o” 或 “2“ ,例
如:c2do, r3p2, r2d2, c3po, 等等。
字符
在正则表达式中有两种字符存在,普通字符(有的书上叫原义文本字符)和元字符。
普通字符:指的是匹配自身,例如abc只能匹配abc
元字符:是在正在表达式中具有特殊意思的字符,用来代替一个字符(除-外)的字符,其本质上看来他还是字符。字符组中的—表不能匹配字符串中横线字符(—),而是用来表示范围的,像这类字符就叫做元字符,像这种元字符在匹配中不在表示他本来的意义了,有着特殊的意义,但有的时候我们需要它的本来的意思,怎么办?此时就必须进行特殊处理。
对于字符组内的-,它只在方括号内才有特殊的含义叫元字符。其他情况下都是本身的意思叫普通字符。元字符取消特殊含义的做法就是转义,也就是在正则表达式前面加上一个反斜杠\
汉字:[\u4E00-\u9FA5]
这里需要强调一下反斜杠\的作用:
- 反斜杠后边跟元字符去除特殊功能;(即将特殊字符转义成普通字符)
- 反斜杠后边跟普通字符实现特殊功能;(即预定义字符)
- 引用序号对应的字组所匹配的字符串。
a=re.search(r'(tina)(fei)haha\2','tinafeihahafei tinafeihahatina').group() print(a) 结果: tinafeihahafei
转义字符
在Python中,转义字符是一种特殊的字符序列,通常以反斜杠 \
开头,用于表示不易输入或打印的字符,或者表示具有特殊含义的字符。注意有些元字符加上/就会变成转义字符了。
Python支持许多标准的转义字符,以下是一些常见的Python中的转义字符以及它们的含义:
转义字符 | 描述 |
---|---|
\(在行尾时) | 续行符 |
\\ | 反斜杠符号 |
\' | 单引号 |
\" | 双引号 |
\a | 响铃 |
\b | 退格(Backspace) |
\e | 转义 |
\000 | 空 |
\n | 换行 |
\v | 纵向制表符 |
\t | 横向制表符 |
\r | 回车 |
\f | 换页 |
\oyy | 八进制数yy代表的字符,例如:\o12代表换行 |
\xyy | 十进制数yy代表的字符,例如:\x0a代表换行 |
\other | 其它的字符以普通格式输出 |
原生字符串
我们知道在某些元字符前边加上\就会变成转义字符,这时候你拿着转义字符去匹配只能匹配到转义字符,不能匹配到元字符。
我们先看一段代码,
import re ret=re.search("\bb","baa[2caaba").group() # 匹配b开头的字符 print(ret)
结果是错误的,它会找不到匹配的内容
怎会这样?
我们想要匹配b开头的,也就是\b是元字符含义,不能是转义字符空格的含义,这时候你就需要把转义字符变成元字符的含义了
原因在于正则表达式在python中是以字符串的形式出现的,正则表达式\b本身表示匹配以..开头的字符串,但是你不要忘记了\b在python字符串中也表示转义,表示退格符,所有上面的代码就会匹配不出来了.如何解决呢,
方法一,在\前面再加一个\就可以了.
import re ret=re.search("\\bb","baa[2caaba").group() print(ret)
结果:
b
import re ret=re.search("a\d","baa2caaba").group() print(ret)
结果:
a2
我总结一下,这个问题,因为\d在字符串中没有转义的意思,只在正则表达式中发挥作用
方法二:
这时候原生字符串的概念就出来了,python提供了原生字符串(RAW String),它非常适合于正则表达式:正则表达式是什么模样,原生字符串就是什么样子,完全不需要考虑正则表达式之外的转义(但是有一个除外就是双引号在原生字符串内的双引号字符必须转义写成\").只需要在字符串前面加一个r,就代表该字符串是原生字符串了.
标准解释:
原生字符串(Raw String)是一种字符串表示方式,通常用于编程语言中,其中反斜杠字符 \
不会被解释为转义字符。在原生字符串中,反斜杠 \
只是一个普通字符,不会用于转义其他字符。在Python中,你可以使用前缀 r
或 R
来创建原生字符串。
原生字符串的主要用途是在处理正则表达式、文件路径、Windows路径等情况下,以防止反斜杠 \
被误解释。例如,Windows文件路径通常包含反斜杠 \
,如果不使用原生字符串,需要双反斜杠 \\
来表示一个反斜杠。而在原生字符串中,一个反斜杠仍然表示一个反斜杠。
import re ret=re.search(r"\bb","baa[2caaba").group() print(ret)
结果:
b
上边括号中的那句话的例子:
import re ret=re.search(r"a\"",'baa"2caaba').group() print(ret)
结果:
a"
对于[ ]:只需要转义\[ 不需要转义了]
如果在方括号里有一个^表示除方括号内的内容外,匹配所有字符。l;比如[^1] :表示匹配除1外的所有字符
量词
量词:限定之前的元素的出现次数,这个元素可能是一个字符,可能是一个字符组,还可能是一个表达式,如果这个表达式用括号括起来的话。
注意 :单独的*+?,等都是贪婪匹配,尽可能的匹配更多次,如*,他会选择0次也会选择多次。
关于?:
量词?有三种含义:
1.单独使用时表示匹配,变成贪婪匹配,尽可能的匹配多次
text = "a test string with a pattern b and some more text" pattern = r"te?" ret = re.search(pattern, text) print(ret.group()) # 输出 te
2.紧跟在量词后面且?后边没有跟别的东西时,变成非贪婪。表示要求搜索引擎匹配的字符串越短越好。例如:(*?)虽然+可以表示0次和更多次但是这里只表示1次。
text = "a test string with a pattern b and some more text" pattern = r"te*?" ret = re.search(pattern, text) print(ret.group()) # 输出 t
import re text = "a test string with a pattern b and some more text" pattern = r"te.*?" # 取最短的0次 ret = re.search(pattern, text) print(ret.group()) #结果te
3.紧跟在量词后面且?后边跟着别的东西时,变成非贪婪模式。包括带括号的这种re.findall(r'(4+?)a', a)
text = "a teest string with a pattern b and some more text" pattern = r"te+?s" # 取+号的1次 ret = re.search(pattern, text) print(ret.group()) # 输出tees
.*?x,取前面任意长度的字符串,直到出现字符x停止。变成非贪婪匹配
import re text = "a teest string with a pattern b and some more text" pattern = r"te.*?b" ret = re.search(pattern, text) print(ret.group()) # 输出 teest string with a pattern b
解释 .*?
匹配任意字符零次或多次,尽可能少地匹配(这里取*的1次而不是0次),直到下一个部分 "b"。
#需求取出生日的年份和本科的年份 import requests,re info = '姓名:bobby1987 生日:1987年10月1日 本科:2005年9月1日' res = re.findall("生日.*?(\d{4})年.*?本科.*?(\d{4})年",info) print(res)
结果:
[('1987', '2005')]
括号
分组
匹配身份证号码,要求身份证号码是一个1 5或18个字符的字符串,如果是15位,则全部是数字,如果是18位,最后一个可能是x,也可能是数字. 不管是15位还是18位,都不能是以0开头.
import re ret=re.search(r"^[1-9]\d{14}(\d{2}[0-9x])?$","37092632666666").group() print(ret)
括号的这种功能,叫做分组,如果用量词限定出现次数的元素不是字符或者字符组,而是几个字符甚至表达式,就应该用括号将他们分为一组,比如希望字符串ab出现的次数为1次以上,就应该写作(ab)+,此时的ab称为了一个整体..
分组命名
分组可以是无名分组,也可以是有名分组,有名分组主要以后Django中url分发中
正则表达式分组命名格式: (?P<分组的名称>正则表达式)
例子:
urlpatterns = [ url(r'^(?P<username>\w+)/',views.home_site), #分组分为有名分组和无名分组,这步的目的是为了给home_site()函数传参, # 如果是有名分组views里的home_site函数中的参数必须和它的名称相同,比如username,如果是无名的只需要传*args就可以了, #其实这步是用了函数的关键字传参,例如home_site(username="alex") ###另外注意这里不要添加admin的url会报错,admin的url只能在主程序中出现 ]
views.py
def home_site(request,username): """ 个人站点函数 :param request: :return: """ # 判断url传过来的username是不是在数据库中存在 user = UserInfo.objects.filter(username=username).first() if not user: return render(request,"error_page.html") #当前站点文章的分类 category_list= HomeCategory.objects.filter(blog=user.blog).values("title") print(category_list) #当前站点所有的文章 article_list=Article.objects.filter(user=user) return render(request,"home_site.html",locals())
多选结构
关于上题的匹配身份证号,可不可以这样分情况处理,15位身份证号就是[1-9]开头,之后是14位数字,18位身份证就是[1-9]开头,之后是16位数字,最后是[0-9x].
这就用到了括号的另外一个功能多选结构.
多选结构的形式是(..|..), 在括号内以竖线分隔开多个子表达式,这些子表达式也就多选分支,在一个多选结构中,多选分支的数目是没有限制,在匹配是整个多选结构视为一个元素,只要其中某个子表达式能匹配上,就算匹配成功,反之匹配失败,
那么这个代码可以这样写([1-9]\d{14}|[1-9]\d{16}[0-9x)
多选结构的匹配顺序:如果你想要从一组字符串中匹配出湖南和湖南省,你应该怎么写?你不应该这样写(湖南|湖南省)他就只把湖南匹配出来,而不会把湖南省匹配出来,所以你应该这样写(湖南省|湖南),但是我们在写代码的时候尽量避免这种匹配,因为效率会很低。
正向引用: 括号不仅仅能把有关联的元素归拢起来并分组,还有其他的作用,使用正则之后,正则表达式会保存每个分组的真正匹配的文本,等到匹配完成后,通过group(num)之类的方法“引用”在匹配是捕获的内容其中num表示对应的括号的编号,括号分组的编号规则是从左向右计数,从1开始。因为捕获了文本,所以这种功能叫捕获分组,对应的,这种括号叫捕获型括号。
括号编组的规则:无论括号如何嵌套,分组的编号都是根据开括号出现的顺序来计数的,开括号是从左向右数起第多少个开括号,整个括号分组的编号就是多少。注意分组编号只取决于开括号出现的顺序、
例子:
b = "2013-01-09" f = re.search("(\d{4})-(\d{2})-(\d{2})",b).group(1) #group中的数据代表要输出第几个括号的值,从1开始 print(f) #结果: 2013 f = re.search("(\d{4})-(\d{2})-(\d{2})",b).group(2) print(f) #结果: 01
我们来看
findall()对于引用括号的处理
b = "2013-01-09" f = re.findall("(\d{4})-(\d{2})-(\d{2})",b) print(f) #结果为:[('2013', '01', '09')] 是一个元祖
反向引用:他允许在正则表达式内部引用之前的捕获的分组匹配的文本(也就是左侧)其形式也是用\num,其中num编号和之前的编号相同。如,我们要匹配两个重复的字母
RE模块
那么在python中是如何用正则表达式的呢?
Re模块下的常用方法:
findall()
findall(正则表达式,字符串,flags) 函数:返回所有满足匹配条件的结果,放在列表里、如果没有找到会返回一个空列表,注意正则表达式也是字符串形式. 同时findall有优先级的问题
# 常用的flags re.I 使匹配对大小写不敏感 re.M 多行匹配,影响 ^ 和 $,这两个字符会匹配每一行的开始和结尾,这个以后再说 re.S 让 '.' 特殊字符匹配任何字符,包括换行符;如果没有这个标记,'.' 就匹配 除了 换行符的其他任意字符。对应内联标记 (?s) 。 re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
如果有括号的它只会匹配括号里边的.
import re ret=re.findall("a{2}","aacaaba") print(ret)
注意对于如果你要使用findall(),需要注意它的findall() 优先级的问题,如果你的表达式中有括号,它只会把你括号内的正则表达式匹配出来
ret=re.findall(r"^[1-9]\d{14}(\d{2}[0-9x])?$","370923199807260555") print(ret)
结果:
['555']
这时候你需要取消它的优先级或者用search()
ret=re.findall(r"^[1-9]\d{14}(?:\d{2}[0-9x])?$","370923199807260555") print(ret)
结果:
['370923199807260555']
例子:
a = 'https://www.zhihu.com/question/279174469/answer/607057185' #提取出a中的这样的格式 'https://www.zhihu.com/question/279174469’ 279174469
import re a = 'https://www.zhihu.com/question/279174469/answer/607057185' r = re.findall('.*question/(\d+)',a) print(r)
结果:
['279174469']
改正取消优先级
import re a = 'https://www.zhihu.com/question/279174469/answer/607057185' r = re.findall('.*question/(?:\d+)', a) print(r) # 结果 ['https://www.zhihu.com/question/279174469']
match()
re.match() ,总是从字符串“开头”去匹配,并返回匹配的字符串的match对象。如果字符串开始不符合正则表达式,则匹配失败,函数返回None;用法同search,但是与search的不同是,re.search匹配整个字符串,直到找到一个匹配。
import re
ret = re.match("hello","dfgdhello")
print(ret.group())
结果:
AttributeError: 'NoneType' object has no attribute 'group'
search()
search(正则,字符串)函数: 在字符串中查找匹配内容只要找到第一个匹配内容则返回,如果没有找到则返回None.
import re a = 'https://www.zhihu.com/question/279174469/answer/607057185' r = re.search('.*question/(\d+)', a).group() print(r) # 结果 https://www.zhihu.com/question/279174469
注意:
match和search一旦匹配成功,就是一个match object对象和search对象,而match 和search object对象有以下方法:
- group() 返回被 RE 匹配的字符串
- start() 返回匹配到的第一个内容所在的位置索引,前提是匹配上的结果占一个索引
- end() 返回匹配到的最后一个内容所在的位置索引,前提是匹配上的结果占一个索引
- span() 返回一个元组包含匹配 (开始,结束) 的位置索引
- group(数字) 返回re整体匹配的字符串或根据输入的组的编号,对应组号匹配的字符串。这种形式主要用在正则表达式中的分组.
- groups() 如果正则里有括号时返回所有括号里匹配的内容
start()
import re ret=re.search("a{2}","baacaaba").start() #这里的aa占一个索引 print(ret)
结果:
1
end()
import re ret=re.search("a{2}","baacaaba").end() print(ret)
结果:
3
span():
import re ret=re.search("a{2}","baacaaba").span() print(ret)
结果:
(1, 3)
group(数字),我们知道findall有括号优先级,只匹配括号里的正则,那么search也有这个功能,需要在group里添加数字.数字表示匹配第几个括号内的正则,当数字为0或者不写时,匹配所有的正则包括有括号的和没有括号的,当数字为1时匹配第一个括号里的正则,其他同理,如果写的数字没有对应的括号,则会报错
import re a = "123abc456" print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0)) #123abc456,返回整体 print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1)) #123 print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2)) #abc print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3)) #456 ###group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。###
一个group(0)的例子
import re a = "44aa66bb478" b = re.search(r'(4\w?).*?(b\w?)',a).group(0) print(b) #结果 44aa66bb
sub()
re.sub(pattern,replacement,string) : pattern:表示正则表达式用来匹配被替换文本的。replacement:需要替换成的东西可以是正则表达式,string要替换的字符串。利用正则表达式替换字符串中的字符。
范例:
import re ret=re.sub(r"[a-z]","hello","1kj2kj3kj") print(ret)
结果:
1hellohello2hellohello3hellohello
re.sub也可以使用引用分组的知识
b = "2013-01-09你好" f = re.sub(r"(\d{4})-(\d{2})-(\d{2})",r"\1年\2月\3日",b) #这里使用的引用分组表示方法为\num num就是第几个分组 print(f) #结果为:2013年01月09日你好
compile()
re.compile()
是 Python 中用于编译正则表达式模式的函数。它将一个正则表达式模式编译为一个正则表达式对象,然后可以使用该对象进行更高效的模式匹配。使用 re.compile()
的主要优点是可以提高性能,特别是在需要多次执行相同模式匹配的情况下。
以下是关于 re.compile()
函数的一些关键信息和用法:
语法:
compiled_pattern = re.compile(pattern, flags=0)
pattern:要编译的正则表达式模式,通常为字符串。
flags(可选):用于指定正则表达式的标志,例如忽略大小写、多行模式等。默认值为0,表示没有标志。
-
返回值:
re.compile()
返回一个正则表达式对象,该对象可以用于执行模式匹配操作。
-
使用方法:
- 使用
re.compile()
编译后的正则表达式对象可以调用正则表达式方法,如search()
、match()
、findall()
等,来执行模式匹配操作。 - 通过使用编译后的正则表达式对象,可以避免每次执行匹配时都重新编译模式,从而提高了匹配的性能。
- 使用
import re # 编译正则表达式模式 pattern = re.compile(r'\d+') # 使用编译后的正则表达式对象进行匹配 text = 'The price of the product is $25.99 and $10 discount.' matches = pattern.findall(text) print(matches) # 输出: ['25', '99', '10'] # 使用同一编译后的模式对象进行多次匹配 another_text = 'There are 3 apples and 5 bananas.' matches2 = pattern.findall(another_text) print(matches2) # 输出: ['3', '5']
在上述示例中,re.compile()
用于编译匹配数字的正则表达式模式,然后可以多次使用编译后的正则表达式对象执行不同文本的模式匹配,而无需每次都重新编译模式。这提高了代码的效率和性能。
compie()函数是在我做项目的时候,遇到的,在flask的项目中,最url过滤器,也就是说app.before_request装饰器不能拦截登录页面和静态页面,当时用的是正则表达式的compile
compile函数用于将正则表达式,生成一个匹配对象,它单独使用没有任何异议供match和search这两个函数使用.
ignore_check_login_urls=[ "^/static", "^/favicon.ico", "^/user/login" ] request_path=request.path #进来的所有url pattern= re.compile("%s"%("|".join(ignore_check_login_urls))) #形成一个对象 print('parrent',pattern #得到结果:re.compile('^/static|^/favicon.ico|^/user/login') if pattern.match(request_path): return false else: v = session.get('login_name') if not v: return redirect('/user/login')
具体就是把正则表达式编译成对象然后调用match方法或search方法,把要匹配的内容当做参数传给该函数.
案例二:
import re def main(): content = 'Hello, I am Jerry, from Chongqing, a montain city, nice to meet you……' regex = re.compile('\w*o\w*') #把含有o的字字母提出来 x = regex.findall(content) print(x) main()
结果:
['Hello', 'from', 'Chongqing', 'montain', 'to', 'you']
当然你直接用re.findall也能得出结果
优先级
findall ()的优先级,findall 会只把组内的信息返回出来,如果想要返回全部匹配信息,则取消优先级即可,在开括号后边加?:
re.findall
是 Python 中用于从文本中查找所有匹配模式的函数,它返回一个包含所有匹配项的列表。在使用 re.findall
时,正则表达式中的模式优先级遵循正则表达式的语法规则。通常情况下,正则表达式的模式按照从左到右的顺序进行匹配,而不同部分之间的优先级由其在正则表达式中的位置和符号规则决定。
以下是一些正则表达式的优先级规则:
-
量词优先级:
- 量词如
*
、+
、?
和{}
具有较高的优先级,它们应用于之前的字符或子模式。 - 例如,正则表达式
a*b+
匹配一个或多个 "a",后跟一个或多个 "b"。
- 量词如
-
括号优先级:
- 圆括号
()
用于指定子模式,可以用于改变模式的匹配顺序和优先级。 - 例如,正则表达式
(ab)+
匹配一个或多个 "ab" 的连续序列。
- 圆括号
-
选择优先级:
|
表示选择,用于指定多个可能的模式之一。- 例如,正则表达式
a|b
匹配 "a" 或 "b" 中的任何一个。
-
锚点和边界优先级:
- 锚点字符如
^
(行的开头)和$
(行的结尾),以及单词边界\b
,用于指定匹配的位置而不是字符。
- 锚点字符如
范例;
import re ret=re.findall("www.oldboy.com","www.oldboy.com") print('1',ret) ret=re.findall("www.(baidu|oldboy).com","www.oldboy.com") print("2",ret) ret=re.findall("www.(?:baidu|oldboy).com","www.oldboy.com") print("3",ret)
结果:
1 ['www.oldboy.com'] 2 ['oldboy'] 3 ['www.oldboy.com']
split
re.split()
是 Python re
模块提供的一个函数,用于根据正则表达式模式拆分字符串。它将字符串分割成多个部分,每个部分都是基于正则表达式模式匹配的位置来定义的。以下是 re.split()
的基本用法:
import re text = "apple,banana,orange" pattern = r"," result = re.split(pattern, text) print(result) # 结果 ['apple', 'banana', 'orange']
的优先级查询,返回一个列表
在匹配部分加上()之后所切出的结果是不同的,
#没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,
import re
ret=re.split("[ab]","eacbcd") #["e" ,"c","cd"]#因为a,a,前面都不是空字符串所有不再切割空字符串
print('1',ret)
ret=re.split("[ab]","eabcd") #["e" ,"","cd"]因为a前面不是空字符串,但b前面时空字符串
print('2',ret)
ret=re.split("[ab]","abcd") # ["","","cd"] #因为ab前面是空字串,把空字符串也切割开来
print('3',ret)
ret=re.split("(ab)","eabcd") #["e" ,"","cd"] #如果加上()把ab当做一个整体也放在列表中
print('4',ret)
ret=re.split("ab","eabcd") #["e" ,"cd"] 如果不上()ab不会放在列表中
print('5',ret)
结果:
1 ['e', 'c', 'cd'] 2 ['e', '', 'cd'] 3 ['', '', 'cd'] 4 ['e', 'ab', 'cd'] 5 ['e', 'cd']
范例:
ret=re.split("\d+","eva3egon4yuan") print(ret) ret=re.split("(\d+)","eva3egon4yuan") print(ret)
结果:
['eva', 'egon', 'yuan'] ['eva', '3', 'egon', '4', 'yuan']
匹配整数:
这里你需要理解清楚一件事情。管道符号(|)如果管道符号两边的任何一边有括号,它会只匹配括号里的,然后把匹配内容返回来。