热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python3正则表达式(Python3RegularExpression)

引子语法字符预定义字符集数量词边界匹配逻辑分组注意事项正则表达式前的r是什么Python3正则工具正则表达式是一种通用的工具,并不只属于Python语言,


  • 引子
  • 语法
    • 字符
    • 预定义字符集
    • 数量词
    • 边界匹配
    • 逻辑分组
  • 注意事项
    • 正则表达式前的r是什么
  • Python3正则工具

正则表达式是一种通用的工具,并不只属于Python语言,基本大部分语言都封装好了这个工具。


引子

正则表达式(Regular Expression)是一种用于做字符串匹配的工具,它能够非常方便地从一段文本中找到/匹配出符合一定要求/规律的目标字符串。

但是我们什么情况下要做字符串匹配呢?而且为什么要用正则表达式做呢,直接用一对一的去对不行吗?举一个简单的例子来回答上面的问题。

比如说,我们有如下一段文本,假设这是你写的一段日记:

我昨天认识了一个女孩A,她给了我她的邮箱girlA@163.com,和她道别后,又遇到另一个女孩B,她也给了我她的邮箱girlB@qq.com,没想到,我之后又遇见了第三个女孩C......

那么,现在,你肯定想做的事情就是给这些遇见的女孩子们群发约会的邀请信息,但是想偷懒的你不想手动一个个地去从上述文本里肉眼查找,然后复制粘贴每个女孩的邮箱(数量少时,你手动做肯定没问题)。于是,你想能不能用一个工具自动提取出所有日记里面的邮箱地址出来。

然后你也想到,首先每个人邮箱的名字都不一样,可能是各种数字与字母的组合,其次邮箱所属的机构名(163, sina, qq, gmail, outlook)也可能不一样,域名(.com, .net, .cn)也可能不一样,那么这可怎么匹配?

但是,聪明的你总结出了一条规律:邮箱不过就是【若干个字符(邮箱名)+@+若干个字符(机构名)+.+若干个字符(域名)】,如果程序能够懂这个模式就能挑选出字符串了!

现在告诉你,正则表达式就可以做到,它就可以按照【若干个字符(邮箱名)+@符号+若干个字符(机构名)+.+若干个字符(域名)】的模式去从文本里把所有符合这个模式的字符串全部找出来。

用python3具体做法如下:

import re
pattern=re.compile(r'[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}\.[com,cn,net]{1,3}')
text='''我昨天认识了一个女孩A,她给了我她的邮箱girlA@163.com,
和她道别后,又遇到另一个女孩B,她也给了我她的邮箱girlB@qq.com,
没想到,我之后又遇见了第三个女孩C......'''

match = pattern.findall(text)
for email in match:print(email)
# girlA@163.com
# girlB@qq.com

搞定!

目前你看不懂上面的代码没关系,下面我们来一一讲解。


语法

要想用好正则表达式,首先要学习正则表达式的使用语法/使用规则。

图来自Python正则表达式指南

我们按照图里面的顺序来分别讲解不同正则表达式不同部分的语法:


字符


  • 一般字符

如”a”,”b”,”g”,”4”,”,”等这种比较常用的字符,在正则表达式中都是一对一地匹配和自身相同的字符,没什么特别。

import re
text='abcdefg'
match=re.search('cd',text)
if match:print(match.group())
# cd 匹配到cd

  • .(点)

.(一个点)用于匹配任意除了换行符”\n”以外的字符。

import re
text='ab3defg'
match=re.search('b..',text)
if match:print(match.group())
# b3d 匹配到b及其后紧跟的两个任意字符

  • \(反斜杠)

反斜杠表示转义字符,它将使得紧跟在它后面的字符转变成特殊的含义,或者消除特殊字符本身的特殊含义。(注意,后面的\d 和\w等正则本身包含的元字符中的反斜杠不属于此类转义作用,而是正则中规定好的组合,元字符里的反斜杠就是普通的反斜杠字符。这一点目前看不懂没关系,结合后面的注意事项部分理解)

比如当\与.(点)结合在一起,点就不再是匹配任意单个字符,而是确实地匹配一个点(消除特殊含义)。


  • […]

中括号中放上任意多的字符,则这些字符会构成一个字符集合,这个模式将会在遇到集合中的任意一个字符时都认定为匹配。

import re
text='abc345def'
match=re.search('[345]',text)
if match:print(match.group())
# bc3 由于匹配到了(3,4,5)中的3,不再继续向后匹配。match=re.search('[b5aef]',text)
if match:print(match.group())
# a 由于匹配到了(b,5,a,e,f)中的a,不再继续向后匹配

预定义字符集


  • \d \D 数字字符

d是decimal(十进位的)首字母,代表数字。w

\d用来匹配任意单个数字(0到9),而\D则是匹配任意的非数字(大写就是反过来,比较好记)

import re
text='abc345def'
match=re.search('\d\d',text)
if match:print(match.group())
# 34 匹配到连续的两个数字match=re.search('\D\D',text)
if match:print(match.group())
# ab 匹配到连续的两个非数字字符

  • \s \S 空白字符

s代表space(空格,空白),可以匹配空格,\t制表符,\r,\n换行符,\f,\v这几个不会在屏幕上输出可见字符的字符。

import re
text='abc3 45def'
match=re.search('\d\s\d',text)
if match:print(match.group())
# 3 4 匹配到数字+空格+数字的三个连续字符的组合

  • \w \W 单词字符

w代表word(单词),能构成word的字符包括大小写的字母(a-z和A-Z),也包括数字(0-9),还包括下划线_。

import re
text='_a3%sdef'
match=re.search('\w\w\w\w',text)
if match:print(match.group())
# sdef 匹配到连续的四个单词字符,但不能匹配"_a3%",因为%是非单词字符

数量词

首先明白,数量词不能单独使用,是配合前面的字符使用,用以描述对前面的字符进行什么数量的匹配。


  • *(星号)

星号*表示对前面的字符串进行0次或多次匹配(多次即大于等于1次)。

import re
text='_a34534%1sdef'
match=re.search('a\d*',text)
if match:print(match.group())
# a34534 匹配到a与其后的多个数字,直到遇到第一个非数字的%号停止匹配。match=re.search('a\s*\d*',text)
if match:print(match.group())
# a34534 依然是匹配到a与其后的多个数字。虽然在字符a与数字之间没有任何空白字符,但是由于星号允许匹配0个(即没有),所以也算匹配。

简而言之,星号是有则匹配,没有也不要紧,继续往后走。


    • (加号)

加号相对星号就“真的很严格~~”,因为它要求前面的字符至少匹配一次,多次也ok。

承接上面星号中的例子,只是把”\s*”改成了”\s+”:

import re
text='_a34534%1sdef'
match=re.search('a\s+\d*',text)
if match:print(match.group())

输出为空白,因为字符a和后面的数字之间根本没有空白字符,于是匹配不成功,返回空。

import re
text='_a34534%1sdef'
match=re.search('a\d+',text)
if match:print(match.group())
# a34534 这样就还是能匹配到字符a及后面的数字。

  • ? (问号)

问号就是确认一下有木有,有或没有都行,但是你有的话,就只能有一个,不准多了,多了它不负责匹配。

import re
text='_a34534%1sdef'
match=re.search('a\d?',text)
if match:print(match.group())
# a3 匹配到a及后面紧接的一个数字,不再继续向后匹配。match=re.search('a\d*!?',text)
if match:print(match.group())
# a34534 匹配到a及后面的数字,又尝试匹配数字后的感叹号,发现没有(实际是百分号),于是停止匹配。

  • {m}

这个就比较简单,就是匹配前一个字符m次。

import re
text='_a34534%1sdef'
match=re.search('\d{3}',text)
if match:print(match.group())
# 345 匹配数字字符三次。

  • {m,n}

这个也很简单,就是匹配n到m次,即大于等于n,小于等于m的任一次数都可以。不过会尽量往多的次数匹配。

import re
text='_a34534%1sdef'
match=re.search('\d{1,4}',text)
if match:print(match.group())
# 3453 匹配数字1-4次,因为尽量往多的匹配,匹配了4个数字match=re.search('\d{1,3}s',text)
if match:print(match.group())
# 1s 匹配数字1-3次及后面的s字符

  • *? +? ?? {m,n}?

注意,这里的问号不是前面讲的“匹配字符0或1次”的那个问号的作用,而是用于声明使用懒惰模式(或者叫非贪婪模式)来进行匹配。

什么叫贪婪模式?即默认情况下,凡是涉及到数量词的匹配,正则表达式的匹配工具都是尽可能地往次数多的去匹配的(例子见上面讲{m,n}时的第一个示例代码),这就是贪婪模式(尽可能多就是很“贪婪”嘛~)。而非贪婪模式/懒惰模式就是相反地做尽可能少的匹配。

两种情况下,星号、加号、问号、{m,n}实际上就分别成了以下作用:


符号贪婪模式非贪婪模式
*多次(无限次)0次
+多次(无限次)1次
?1次0次
{m,n}n次m次

举个简单的例子:

import re
text='_a34534%1sdef'
match=re.search('\d{2,3}',text)
if match:print(match.group())
# 345 匹配到连续的三个数字(贪婪模式)match=re.search('\d{2,3}?',text)
if match:print(match.group())
# 34 匹配到连续的两个数字(非贪婪模式)

边界匹配


  • ^ $

^代表匹配字符串的开头(在多行模式中匹配每一行的开头)。$代表匹配字符串的末尾(在多行模式中匹配每一行的末尾)。

这两个符号主要是针对只想匹配文本的开头或结尾部分的情况使用的。而当一个正则表达式同时使用两个符号”^XXXXXX$”时,意思就是对字符串进行从头到尾的整体匹配。

比如我们在任意网站注册账号时要求填入邮箱信息,那么网站的后台一般会对邮箱做合法性检验,那么就会对我们输入的邮箱地址做从头到尾的正则匹配,看是否符合要求。

import re
text='%a1234'
match=re.search('a\d{4}',text)
if match:print(match.group())
# a1234 匹配到a和连续的4个数字match=re.search('^a\d{4}',text)
if match:print(match.group())
# 输出空白,因为从头开始匹配,而开头是百分号,不是a,匹配失败。

  • \A \Z

\A和^的作用相同,\Z和$的作用相同。


逻辑、分组


  • (…)分组

圆括号会将包含在里面的所有内容作为一个分组的整体,我们可以在分组加数量词来匹配分组若干次。

import re
text='_a34a534b123%1sdef'
match=re.search('(a\d{2,3})+',text)
if match:print(match.group())
# a34a534 匹配分组[字符a+2-3个数字]一次或多次

  • |(竖线)

竖线本身会将正则表达式切分成左右两个部分,而竖线的作用就是:优先匹配左边的部分,如果匹配到了,就完成匹配;如果未匹配到,再匹配右边的部分,如果匹配到了,就完成匹配,否则,匹配失败。

import re
text='_a34a524b1234%1sdef'
match=re.search('[a-z]\d{5}|[a-z]\d{4}',text)
if match:print(match.group())
# b1234 竖线左边小写字母和5个数字的匹配未成功,转而匹配右边小写字母和4个数字的匹配成功。

常用的正则语法也就是上面这些,其它的大家可以再需要用到时再回来看和理解,这里就先不详细讲解了。


注意事项


正则表达式前的r是什么

r表示raw(原生的,未加工的),放在字符串前,表示这个字符串为原生字符串,而原生字符串指的是区别于正则表达式的一般字符串。

强调原生字符串这种概念的主要原因是针对反斜杠(\)的问题。因为反斜杠在一般编程语言的字符串里的含义是转义符,转义符的作用在前面也有提到过,就是转换或消除后面紧跟的一个字符的特殊含义。即单个的反斜杠出现时,就会被认为是在对紧跟在它后面的一个字符进行转义操作。

但是问题是,有时候我们可能想匹配的文本就是一个包含有反斜杠字符的字符串,那么我们在正则里面就需要把反斜杠的转义的作用给消除掉,而表达出我们就是要匹配一个反斜杠字符的意思。

首先,我们要明白的是,如果我们想在一个普通字符串里面表达一个反斜杠字符,而非转义符时,应该这么做:

string='abc\ndef'
print(string)
# abc
# def
# 会分两行输出abc和def,因为\n中的反斜杠被认为成转义符,和n结合时就成了换行符。# 第一种方法:反斜杠+反斜杠
string='abc\\ndef'
print(string)
# abc\ndef
# 只输出一行,因为\\n中的第一个反斜杠作为转义符,将后面的第二个反斜杠给转义了,使其成为了普通的反斜杠字符(消除特殊含义)。# 第二种方法:声明原生字符串
string=r'abc\ndef'
print(string)
# abc\ndef
# 只输出一行,因为被声明成原生字符串的字符串,其里面所有的字符都将被消除特殊含义,于是单个的反斜杠也不是转义符了,而成了普通的反斜杠字符。

好,回到我们的正则表达式,正则表达式里如果我们想写一个能匹配反斜杠的表达式,怎么写呢?

import re
text=r'%abc\ndef'
match=re.search('\\\\',text)
if match:print(match.group())
# \

对,没有错,你得写四个反斜杠才行!!!原因是,一般的字符串里你想表达一个反斜杠字符得用两个反斜杠(\),但是在正则表达式里规定了:表达一个普通字符串里的反斜杠字符需要用两个反斜杠字符(很绕很奇怪我知道- -!),于是这代表着,你需要组合两个反斜杠字符(\),即四个反斜杠一起,才能在正则表达式里面表达出一个反斜杠字符的含义

于是,为了简化这个问题,防止在正则表达式里面写反斜杠写到晕厥,我们可以用原生字符串的方法来减少麻烦:

import re
text=r'%abc\ndef'
match=re.search(r'\\',text) # 不是转义
if match:print(match.group())
# \

即,当你把正则表达式声明为原生字符串时,里面所有的反斜杠也不再具备转义含义,而是纯粹的反斜杠字符,于是只用写两个就可以表示普通字符串里面的一个反斜杠字符了。(所以追根溯源,这个坑就是因为正则里面规定了表达式里的两个反斜杠字符才能算普通字符串里的一个反斜杠字符)。

总结:一般情况下,我们也提倡在正则表达式的前面加个r声明这是原生字符串,因为可以减少匹配反斜杠字符时候的麻烦(写两个反斜杠就够了),也不影响其它正则元字符的正常使用(\d,\s,\w等),该咋写咋写。

这个部分比较绕,不过实际情况中也并没有很多需要做反斜杠字符匹配的地方,所以不太理解此部分问题也不大,能够顺畅使用正则表达式里的上述其它语法也已经可以应对绝大多数匹配问题了。

另外,字符串前面有时还有带一个u字母的,意思是表示该字符串是一个unicode编码的字符串。详情参见python字符串前缀 u和r的区别。


Python3正则工具


推荐阅读
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文详细介绍了Python中正则表达式和re模块的使用方法。首先解释了转义符的作用,以及如何在字符串中包含特殊字符。然后介绍了re模块的功能和常用方法。通过学习本文,读者可以掌握正则表达式的基本概念和使用技巧,进一步提高Python编程能力。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
author-avatar
mobiledu2502876867
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有