Python与正则表达式

一、初始

在编程中,字符串往往是我们用到的最多的一类数据结构,但有时十分复杂的字符串处理起来往往十分令人头疼,为了更好的实现对高级文本进行匹配、抽取、搜索、替换等功能,正则表达式(简称为regex)应运而生;

什么是正则表达式呢?

简单的说,正则表达式是一些由字符和特殊符号组成的字符串,它们描述了一种模式或者多种模式或者仅仅是用来表述几个字符;

那么肯定又有人问了,什么是模式?

模式是用来匹配字符串的模板,一种模式可能会匹配到多个符合条件的字符串;比如我随便写一个模式:bit ,按照模式 bit 会匹配到的字符串也只可能是 bit ,因为这个模式没有特殊符号,它描述的字符串也十分精准,即只可能是 bit ;但如果我再写一个模式 b[a-z]t ,可以看到这个模式很明显与上一个有很大区别,在它里面添加了特殊符号 [] 以及 - ,那么它匹配到的将是 bat、bbt、bct…bzt 中任何一个皆有可能。
正则表达式中有十分多的特殊符号,通过学习它们,我们可以匹配出很多复杂的字符串结构并且得到我们想要的结果;

那么怎么匹配呢?

在讨论与字符串模式相关的正则表达式时,匹配指的是模式匹配,而在Python中,主要有两种方法完成模式匹配,即:
匹配(match)搜索(search)
关于它们的具体用法将在下文进行展示;

二、特殊符号

上面讲到,正则表达式有许多特殊符号,它们使得正则表达式灵活多变,充满活力,学习这些特殊符号的用法是学习正则表达式的必经路径,下面将介绍正则表达式中最常见的特殊符号与字符;

1.择一匹配符

择一匹配符 (|) 表示从多个模式中任选其一的操作;

示例:

模式匹配的字符串
bug|big|pigbug、big、pig
42a|27d42a、27d

2.句点匹配符

句点匹配符 (.) 匹配除了换行符\n之外的任何字符,注意是任何字符,不局限于字母和数字、或者可打印字符、不可打印字符;另外,在Python的正则表达式中有一个编译标记,该标记可以使句点匹配符匹配换行符;

示例:

模式匹配的字符串
s.x匹配长度为3的字符串,且开头和结尾是s和x,可以是six、sxx、sex等等
匹配任意三个字符
\.通过转义符匹配句点

3.边界匹配符

边界匹配符用于在字符串的起始或者结尾部分指定用于搜索的模式,涉及到的特殊字符有:
匹配字符串的开始位置:(^) 或者 (\A)
匹配字符串的末尾位置:($) 或者 (\Z)

示例:

模式匹配的字符串
^Hello匹配任何以Hello作为起始的字符串
Byebye$匹配任何以Byebye作为结尾的字符串
^b7.27$匹配任何由b7.27组成的单独字符串

特殊字符 (\b)、(\B) 也用来匹配字符边界
(\b) 用于匹配单词的开头和结尾,也就是边界部分;
(\B) 用于匹配单词的中间部分,非边界部分;

示例:

模式匹配的字符串
\bnice匹配nice前有边界的字符串
nice\b匹配nice结尾有边界的字符串
\bnice\b匹配nice单独字符串
\Bnice匹配任何包含nice但不以nice作为边界的字符串

4.字符集

方括号 ([]) 用于创建一个字符集,这种正则表达式能够匹配方括号中的任何字符;

示例:

模式匹配的字符串
[bp][iu]gbig、bug、pig、pug

附加:
(-) : 限定范围的特殊符号;
([^) : 在字符集中表否定的特殊符号;

示例:

模式匹配的字符串
[2000-2018]-[1-12]-[1-31]匹配2000-5-20等所有满足条件的表日期的特殊字符串
[^\t\n]不匹配制表符或者换行符

5.闭包操作符

(*) :匹配零次或者多次出现的正则表达式;
(+) : 匹配一次或者多次出现的正则表达式,也叫正闭包操作符;
(?): 匹配零次或者一次出现的正则表达式;
({}): 匹配指定次数或指定范围出现的正则表达式

示例:

模式匹配的字符串
[bp]i[gt]?先匹配b或者p,紧跟i,后面最多一个g或者t:bi、pi、big、bit、pig、pit
[0-9]{9,10}匹配9个或者10个数字,可能是qq号码等
[0-9]+.[0-9]*匹配表示浮点数的字符串

6.特殊字符表示字符集

一些特殊符号可以表示一个字符集,比如:
(\d) :表示字符集 [0-9];
(\w) :表示字符集 [a-zA-Z0-9_];
(\s) :表示空格字符,包括Tab等空白符;
以上特殊字符的大写版本表示不匹配,比如 \D 表示字符集 [^0-9];

7.使用圆括号指定分组

有时我们想把匹配到的字符串分组存储或者处理,这时使用圆括号分别包裹相应的正则表达式;
在正则表达式中,一对圆括号可以实现以下功能

  • 对正则表达式进行分组;
  • 匹配子组;

关于分组的更详细用法下面会结合实例介绍;

8.拓展表示法

拓展表示法通常用于在判断之前提供标记,实现预匹配或者条件检查的功能;
拓展表示法的使用符号为 (?..),需要注意的是拓展表示法虽然用到了括号,但是它不具备分组的功能;
示例:

模式匹配的字符串
(?:\w+.)*匹配以句点作为结尾的字符串,但这些匹配不会被保存下来
(?#comment)此处不匹配,只作为注释
(?=.com)如果一个字符串后面跟着.com才做匹配操作
(?!.net)如果一个字符串后面不是.net才做匹配操作
(?<=abc)如果一个字符串之前为abc才做匹配

三、Python与正则表达式

正则表达式的基本知识已经输送完毕,那么接下来让我们利用Python来应用它们;
首先呢,Python当然是支持正则表达式的,Python通过re模块来支持正则表达式,下面就来学习这个模块;

Python的re模块里面有许多常见的函数和方法,接下来将由重及轻的逐一介绍并辅以示例;

1.match()与search()

match()与search()都是用于模式匹配的函数,它们两个的主要区别在于:
match()总是试图从字符串的开始位置进行匹配,而search()总是试图在字符串的任意位置开始匹配;

对match()函数,如果匹配成功,返回匹配对象,否则返回None
使用示例:

import re
m = re.match('mate','mates')
if m is not None:
    print(m.group())
    print(type(m)))
##
mate
<class 're.Match'> 

可以看到成功匹配出字符串bit并返回该匹配对象,该匹配对象的类型为 re.Match,输出匹配对象的内容时,使用了方法group(),关于group()下面会提到,现在只需要知道它的功能是返回对象的内容,如果只是单纯的使用print(m)的话,输出的将是带参数的re.Match object的说明;

注意到这里额外有一条if判断语句,只有m不是None才使用group()方法输出,因为None没有group()方法,这样做可以避免匹配失败时导致程序的AttributeError错误;

参照示例:

import re
print(re.match('mate','mates').group()))

另外,由于match()是从头匹配,所以很容易产生匹配错误,如下:

import re
m = re.match('mate','classmate')
if m is not None:
    print(m.group())

这段程序运行后无输出,因为match()在classmate的开头找不到mate;

这时就需要用到search()函数了:

m = re.search('mate','classmate')
if m is not None:
    print(m.group()) #mate

2.group()与groups()方法

group()与groups()都是正则匹配对象的方法,即 re.Match object方法

group()用于返回整个匹配对象,或者根据要求返回特定子组;
groups()则仅返回一个包含唯一或者全部子组的元组;

这里注意的是,如果没有子组要求,那么groups()将返回一个空元组

示例:

s = 'pig|bug|bit|'
m = re.match(s,'pigduang')
print(m.group())
print(m.groups())
##
pig
()

s = '(\d+)(\.\d*)' #这里用到了()将正则表达式分组,本例即把整数部分与小数部分分组
m = re.match(s,'9.727')
print(m.group())
print(m.groups())
##
9.727
('9', '.727')

另外,其实group()方法也能够访问子组,通过传入参数,其中1表示输出第一个子组,以此类推:

s = '(\d+)(\.\d*)'
m = re.match(s,'9.727')
print(m.group(1))
print(m.group(2))
##
9
.727

3.\b 与 \B

对于上文提到的正则表达式中的边界符号 (\b)与(\B),这里结合示例再进行一个说明:

m = re.search(r'\bpen','The pen is mine') #因为单词pen前面有边界,这里的边界指除字母、数字、下划线之外的其它字符
if m is not None:print(m.group()) #pen

m = re.search(r'\bpen','Thepen is mine')
if m is not None:print(m.group()) #pen前无边界,无输出

m = re.search(r'pen\b','Thepen is mine') #pen后有边界,成功输出
if m is not None:print(m.group()) #pen 

\B用法与之类似,不再赘述

4.compile()函数

在Python中,字符串想要使用是需要经过Python解释器的编译的,每一次使用,Python解释器都会把字符串对象编译成代码对象,这样的话可想而知,如果对于某一字符串需要重复调用,那么将造成效率的折损,这时可以借用eval()函数预编译来提高性能,这是因为预编译的代码对象使用起来要比直接使用字符串要快的缘故;
预编译对于需要重复使用的对象往往很有用,当然这对正则表达式对象也是;提供给正则表达式预编译功能的是re模块下的compile()函数:

s = re.compile(r'\w+\.\w+\.com')
print(type(s))
m = re.search(s,'www.bilibili.com')
print(type(m))
print(m.group())
##
<class 're.Pattern'> #被编译后的类型为 re.Pattern
<class 're.Match'>
www.bilibili.com

5.findall()与finditer()函数

findall():
findall()查询字符串中某个正则表达式模式全部的非重复出现情况,并返回一个列表,如果未找到匹配部分,就返回空列表

示例:

print(re.findall('gen','gen')) 
#['gen']

print(re.findall('gen','a generous general'))
#['gen', 'gen']

可以看到,findall()返回的列表将包含字符串中每一个成功匹配到的部分;这也是它与match()与search()的不同之处,findall会匹配字符串中的所有,但是match()和search()只要匹配成功就不会再管剩下的还没有匹配的部分,比如:

print(re.search('gen','a generous general').group())
#gen

当正则表达式指定子组的话,那么将返回一个元组列表

import re
print(re.findall('(ge(n))','a generous general'))
#[('gen', 'n'), ('gen', 'n')]

另外注意:

s = 'this and that'
print(re.findall(r'(th\w+)',s)) #['this', 'that']
print(re.findall(r'(th\w+) and (th\w+)',s))  #[('this', 'that')]
#以上两种方式注意区分

finditer():
finditer()函数与findall类似,只不过它返回一个迭代对象,这样做的目的则是为了节省内存;
示例:

s = 'this and that'
print(next(re.finditer(r'(th\w+) and (th\w+)',s)))
#<re.Match object; span=(0, 13), match='this and that'>
#注意next()调用迭代器后返回的是一个正则匹配对象,仍是无法直接输出数据的,因此可以:

s = 'this and that'
print(next(re.finditer(r'(th\w+) and (th\w+)',s)).groups())
#('this', 'that')

#也可以用group()
s = 'this and that'
print(next(re.finditer(r'(th\w+) and (th\w+)',s)).group(1))
#this

#也可以用for循环遍历迭代器
s = 'this and that'
for g in re.finditer(r'(th\w+) and (th\w+)',s):
    print(g.groups())
##
('this', 'that')

1-5、附加:
以上所提到的函数,即search()、match()、compile()、findall()、finditer()函数,除了上面介绍的常用使用方法外,它们都还有第三个参数flags,flags可以指定,也可以不指定,它主要是用来提供其它附加条件,比如不区分大小写的匹配或者让句点匹配符能够匹配回车符等等,具体使用可以参阅官方说明文档;

6.sub()与subn()函数

sub()与subn()函数用于实现搜索与替换功能,先来看sub():

sub():
语法格式:

re.sub(pattern,repl,string,count=0,flags=0)
  • pattern :模式字符串,即正则表达式;
  • repl :替换的字符串,也可以是一个函数;
  • string :原字符串;
  • count :替换的次数,默认为0,即匹配并替换全部;
  • flags :编译时用的匹配模式;

示例:

print(re.sub(r'Mr\.\w+','Ms.White','Hello,Mr.Black'))
#Hello,Ms.White

subn():
subn()几乎和sub()一样,唯一不同的是,subn()不仅返回替换后的字符串,还将替换次数返回,这两个返回值作为一个元组返回;

print(re.subn(r'Mr\.\w+','Ms.White','Hello,Mr.Black'))
#('Hello,Ms.White', 1)

7.split()函数:

re.split()与str.split()都是分割字符串的,但re.split()是基于正则表达式之上对字符串进行分割,它能够处理更加复杂的字符串结构,这点是str.split()无法比拟的;
语法格式:

re.split(pattern,str,maxsplit=0,flags=0)

后两个参数可选:

  • pattern :正则表达式;
  • str :待分割字符串;
  • maxsplit :分割次数,默认为0,即不限制次数;
  • flags :控制匹配方式;

示例:

s = '130-001@abc.opk'
print(re.split('-|@|\.',s))
#['130', '001', 'abc', 'opk']

参考
《Python核心编程》
菜鸟编程
廖雪峰的官方网站

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值