Python中的re模块--正则表达式
Python中的re模块--正则表达式
使用match从字符串开头匹配
以匹配国内手机号为例,通常手机号为11位,以1开头。大概是这样13509094747
,(这个号码是我随便写的,请不要拨打),我们通常还能看到其他美观的显示形式。
- 135-0909-4747
- 135 0909 4747
前三位由运营商规定,这里我们不考虑。
如何使用正则表达式匹配类似上面的手机号呢?
import re
result = re.match('\d\d\d-\d\d\d\d-\d\d\d\d', '135-0909-4747')
print(result)
\d
表示匹配一个数字。于是上面的写法可以匹配,但是打印的内容是这样的
# out
<_sre.SRE_Match object; span=(0, 13), match='135-0909-4747'>
没有出现None
说明匹配成功了,字符范围[0, 13],十一位的手机号加上两位分隔符-
刚好13位。match里显示了匹配成功的字符串。这样的结果并不直观。
使用result.group()
即可提取出match里面的内容。并且是str类型,更方便我们处理。
...
print(result.group()) # out: 135-0909-4747
上面的写法还是太臃肿,result = re.match('\d{3}-\d{4}-\d{4}', '135-0909-4747')
,这种写法和上面等价。{}
里面的次数表示要匹配的次数。当然里面可以填区间,区间是闭区间,包含左右的数字。比如
\d{3,}
匹配数字3或者3次以上\d{,9}
匹配数字0次~9次之间\d{2,4}
匹配数字2次~4次之间
一定要注意,填入区间的时候,逗号左右都没有空格。
如果一个规则我们经常要用到,可以使用re.compile
编译成一个pattern object
对象。像这样
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.match(phone_p, '135-0909-4747')
print(result)
# result = phone_p.match('135-0909-4747')
# print(result)
phone_p
是一个对象,可以用它直接调用match
方法,直接填入要匹配的字符串就好了。就像上面被注释掉的地方一样。也可以使用re.match
,不同的是,第一个参数需要填上这个模式对象,第二个参数才是要匹配的字符串。两种方法得到的结果一样,喜欢哪种用哪种。
使用search搜寻字符串中可能存在的匹配
re还有一个serach
方法,和match
用法极其相似。唯有不同的是,match
要求匹配必须从字符串的开头开始,也就是说,如果第一个字符就不匹配,后面即使有和模式匹配的字符串,也被认为是匹配失败。这么说不好理解。举个例子,还是手机号。
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.match(phone_p, 'Bob 135-0909-4747')
print(result)
在手机号前加了机主姓名,我们可以看到,后面还是以前的手机没有变,按理说这个模式应该能提取出手机号,但是打印的却是None
,因为使用的是match
匹配,模式中要求是3个数字打头,然后给出的字符串以字母开始。第一个字符就挂掉了。所以说match是从字符的开头匹配的。
再看看search
呢?
只需将match改成search,输出<_sre.SRE_Match object; span=(4, 17), match='135-0909-4747'>
表示匹配成功,字符范围[4:17],不含17。可以看到search
搜寻字符串里所有可能的情况,一旦发现有匹配的子字符串就返回。
为了加深理解,再看这样的例子
import re
# 注意多了个^
phone_p = re.compile('^\d{3}-\d{4}-\d{4}')
result = re.search(phone_p, 'Bob 135-0909-4747')
print(result) # None
再模式的最前面加上^
表示匹配开始的标志,即必须以^
后的内容开头,在这句里的意思就是必须以3个数字开头(而不是1个,\d{3}
是一个整体)。可以看到,即使是search
方法也不能匹配成功了。因被强制从字符串开头处开始匹配,这句的意思不就和和使用match
方法达到同样的效果了吗?
说到^
就不得不提$
,后者是匹配结束的标志,必须以$
前的字符结尾。
import re
phone_p = re.compile('^\d{3}-\d{4}-\d{4}$')
# 不小心在开头或者结尾多输入了一位
result = re.search(phone_p, '135-0909-47475') # or 1135-0909-4747
print(result) # None
显然结是4个数字结尾(或不是3个数字开头),返回None。
这句模式限制了必须是11位的数字加分隔符组成。多一位少一位都不行。
还有一个地方要注意,不管是match
还是search
,即使可能存在多个正确的匹配,它们找到第一个后就立即停止,所有我们得到的永远是第一个成功匹配的字符串。
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.search(phone_p, 'My phone number is 135-0909-4747 and another is 123-4567-8901')
print(result) # 135-0909-4747
找到第一个手机号就不在匹配了,第二个手机号被忽略了。
使用findall找到所有成功的匹配
上面的例子,如何找到所有的手机号呢?用re.findall
,它返回所有成功匹配字符串的列表.
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.findall(phone_p, 'My phone number is 135-0909-4747 and another is 123-4567-8901')
print(result)
仅是将search换成findall,会打印['135-0909-4747', '123-4567-8901']
可以看到,所有的手机号都被找到了!
在正则表达式中尽量使用原始字符串
由于正则表达式中经常要用到\
,而转义字符可能影响到我们的模式表达。
p = re.compile('gg\\d')
p_1 = re.compile('gg\d')
print('\d') # \d
print('\\d') # \d
上面的例子,打印结果都一样\d
,因为\d
没有对应的转义。两种模式的写法也没有区别。
但是有些字符是可以转义的,比如n。
print('\n') # 换行
print('\\n') # \n
上面例子,结果就不一样了。又回到正则表达式中来
p_0 = re.compile('gg\n') # 匹配'gg\n', \n换行
p_1= re.compile('gg\\n') # 匹配'gg\n', \n换行
# 使用了原始字符串
p_2 = re.compile(r'gg\n') # 匹配'gg\n',\n换行
p_3 = re.compile(r'gg\\n') # 匹配'gg\\n', \n字符串
可以看到没有使用原始字符串时候,会让人迷惑,上述前两行,两种匹配模式匹配的都是gg和一个换行符。使用了原始字符串就比较清楚了,待匹配的字符串(就不要再使用原始字符串了),和模式对应起来了,不会混淆,如上述的最后两行代码。
当然打印的时候又会有些不一样
print('gg\\n') # gg\n
print('gg\n') # gg换行
print(r'gg\n') # gg\n
print(r'gg\\n') # gg\\n
打印时,原始字符串完全忽略了\
对字符的转义,字符串里是啥样,打印出来就是啥样。
在正则表达式里面的原始字符串(对\
还是有一定程度的影响)和打印时候的原始字符串还时有点差别的。
原始字符串在处理文件路径时相当有用。
# 这么写不对,会被转义,结果就是路径错了
filepath = 'F:\nb\person\a.txt'
# 保险一点的做法,用\\将自身转义,表示真正意义上的'\',
filepath = 'F:\\nb\person\\a.txt'
# 使用原始字符串
filepath = r'F:\nb\person\a.txt'
当然了,直接用Linux/OS X的路径方式在Windows上貌似也是可以的。直接远离了转义字符的困扰。
filepath = 'F:/nb/person/a.txt'
也可以运行成功,没问题。
讨论了这么多其实就想说,正则表达式编译模式时,尽可能地使用原始字符串。
高级匹配模式
"[]"匹配集合里面的任意一个字符
import re
p = re.compile(r'[朱刘马]帅吃饭了吗')
result = re.match(p, '马帅吃饭了吗') # or 朱帅吃饭了吗 or 刘帅吃饭了吗
print(result)
[]
里面的内容表示任意一个字符,只要在这个集合里面的就能匹配成功。所以上面的模式可以匹配
马帅吃饭了吗
朱帅吃饭了吗
刘帅吃饭了吗
这是针对单个字符的,还可以这样写[a-z0-9]
代表一个范围。这表示一个字符只要是字母或者数字就能匹配成功,当然后面可以加上{}
。p = re.compile(r'[0-9]{3}')
可以匹配3位数字,其实和\d+{3}
异曲同工。
"|"匹配这个或那个字符串
上面的例子还可以这样写。
import re
p = re.compile(r'朱|刘|马帅吃饭了吗')
result = re.match(p, '马帅吃饭了吗')
print(result)
效果和上面一样。这是单个字符的时候,来看看涉及到特定的多个字符时候。
import re
p = re.compile(r'Bob|Jerry|Tom Lee')
result = re.match(p, 'Jerry Lee')
print(result)
这能匹配三个人名
Bob Lee
Jerry Lee
Tom Lee
如果使用[]
就不好操作了。下面也能匹配上面的三个名字,不过哪个更易懂不言而喻。所以要分场合用最合适的。
p = re.compile(r'[BJT][oe][brm][\sry]{,2} Lee')
还有一点,[]
里可以使用^
表示“非”的意思。
p = re.compile(r'[^0-9]')
这就表示,除开数字的其他任意一个字符。
“?”匹配0次或者1次
import re
p = re.compile(r'我有一万?元')
result = re.match(p, '我有一元') # or我有一万元
print(result)
“万”字匹配0次(没有)或者1次都是成功的。通俗点讲,这个字符时可选的。其实用?
可以看成是p = re.compile(r'我有一万{,1}元')
的简写。
“*”匹配任意次, "+"匹配至少1次
*
可以匹配0次,也可以匹配多次。实际上可看作p = re.compile(r'我有一万{0,}元')
+
匹配至少一次,可以看作p = re.compile(r'我有一万{1,}元')
.这意味着它不能匹配我有一元
,必须含有一个或者多个“万”字。
贪婪匹配和非贪婪
Python的正则表达式默认是贪婪匹配。这意味着它将尽可能多的,尽可能往后匹配。只要后面还有能成功匹配的字符串,就不会停下来。
比如
import re
p = re.compile(r'我有一万*')
result = re.match(p, '我有一万万万万万')
print(result)
虽然*
可匹配0次,1次...多次。但是不是返回我有一
或者我有一万
,而是后面有多少就匹配到多少。
如果要变成非贪婪匹配呢?后加?
p = re.compile(r'我有一万*?')
result = re.match(p, '我有一万万万万万')
这样就会尽可能少的匹配,因为*
最少能匹配0次,所以这里返回我有一
。
注意,这里的?不要解释成0次或者1次,在非贪婪里面的?
和上面介绍的?
是有差别的。
通配字符"."
.
可以匹配除了换行符之外的所有字符,如果加入标志位flags=re.DOTALL
,使得.
什么都可以匹配(包括换行符),还有re.IGNORECASE
和re.VERBOSE
# re.DOTALL
p = re.compile(r'good.haha', re.DOTALL)
result = re.findall(p, 'good\nhaha')
# 按位或可以同时使用两种模式
p = re.compile(r'good.haha', re.IGNORECASE | re.DOTALL)
result = re.findall(p, 'GOOD\nHahA')
# re.VERBOSE可以忽略空白字符和注释,当模式比较复杂时这样可能会直观点
p = re.compile(r'''
\w+. # asdf
\w+''' # some..
, re.IGNORECASE | re.DOTALL | re.VERBOSE)
result = re.findall(p, 'GOOD\nHahA')
顺便一提,\w
匹配单词字符,它包括了数字
搭配*
和?
更好用
.* 贪婪匹配所有字符
.*? 非贪婪匹配所有字符
举个例子
import re
#贪婪
p = re.compile(r'abcd.*1234', re.DOTALL)
result = re.findall(p, 'abcdDAMN1234IT1234')
print(result) # ['abcdDAMN1234IT1234']全部匹配
# 非贪婪
p = re.compile(r'abcd.*?1234', re.DOTALL)
result = re.findall(p, 'abcdDAMN1234IT1234')
print(result) # ['abcdDAMN1234']遇到第一个1234就停止
使用捕获组
上面的例子如果使用()
将.*?
包含起来,在findall
下将只返回括号里的内容,这很有用,往往我们需要的只是那里面的内容。
import re
p = re.compile(r'abcd(.*?)1234', re.DOTALL)
result = re.findall(p, 'abcdFUCK1234')
# out: ['FUCK']
print(result)
如果有多个括号呢?
import re
p = re.compile(r'[a-z]+((\d+)-(\d+))[a-z]+')
print(result.group(1))
print(result.group(2))
print(result.group(3)))
result = re.findall(p, 'afs123-456gds')
print(result)
可以看到,我们把数字用括号包起来了,这里有3个括号。输出是这样的
[('123-456', '123', '456')]
列表里面实际上是一个元组,分别对应了三个括号里面的值。如果觉得findall
返回的形式不够清楚,可以用group
p = re.compile(r'[a-z]+((\d+)-(\d+))[a-z]+')
result = re.match(p, 'afs123-456gds')
print(result.group()) # afs123-456gds
print(result.group(1)) # 123-456
print(result.group(2)) # 123
print(result.group(3)) # 456
group()
或者group(0)
意思一样,永远放回匹配成功的整个字符串。貌似和括号没有什么关系。不过要是使用group(1)
查看下就会发现,它返回了第一个分组里的内容。上面共有3个分组,所以最多group(3)
,group(4)
就要报错了。发现Python将最外层的括号视为第一组,里面的分组按照从左到右的顺序依次为第二组、第三组。
还能使用groups()
方法,返回所有分组(注意和group()区分)
('123-456', '123', '456')
按照顺序依次是第一第二第三组,这和用findall
返回的数据一样(只是少了列表包围)
分割字符串
使用re.split()
import re
# 以这个模式为分隔符
p = re.compile(r'\d+')
result = re.split(p, 'tom32jerry456haha')
print(result) # ['tom', 'jerry', 'haha']
可以看到,以数字为分隔符,将单词提取出来了。
字符串的替换
还是上面的例子,上面以数字分割,这次让汉字替换掉数字。
import re
p = re.compile(r'\d+')
result = re.sub(p, '中文', 'tom32jerry456haha')
print(result) # tom中文jerry中文haha
如果要用到匹配得文本本身,可以使用\1
和\2
这样的形式,表示使用分组得第一组和第二组,\0
没有这样的写法,这会被当成空字符串
import re
p = re.compile(r'(\d+)abcd(\d+)')
result = re.sub(p, r'\2invert\1', '12345abcd67890')
print(result) # 67890invert12345
有两个分组,r'\2\1'
这里要使用原始字符串,不用的话自己试试看输出啥东西。
表示用分组2invert分组1
得内容替代原字符串。由于分组1为12345,分组2为67890,所以是使用了67890invert12345
代替了原字符串.
哦对了,平常还有一个用得比较多。\s
可以匹配空格/换行符/制表符等等空白字符。其他的,用到的时候再查表吧!
针对我个人日常得使用,掌握这么多应该差不多了。不过有个博客总结得更详细,推荐Python正则表达式指南
by @sunhaiyu
2017.6.24
Python中的re模块--正则表达式的更多相关文章
- python中的re模块——正则表达式
re模块:正则表达式 正则表达式:为匹配字符 import re #导入re模块 #re.findall('正则表达式','被匹配字符') re模块下findall用法 在正则表达式中: \w 表示匹 ...
- 常用正则表达式与python中的re模块
正则表达式是一种通用的字符串匹配技术,不会因为编程语言不一样而发生变化. 部分常用正则表达式规则介绍: . 匹配任意的一个字符串,除了\n * 匹配任意字符串0次或者任意次 \w 匹配字母.数字.下划 ...
- Python中的random模块,来自于Capricorn的实验室
Python中的random模块用于生成随机数.下面介绍一下random模块中最常用的几个函数. random.random random.random()用于生成一个0到1的随机符点数: 0 < ...
- Python中的logging模块
http://python.jobbole.com/86887/ 最近修改了项目里的logging相关功能,用到了python标准库里的logging模块,在此做一些记录.主要是从官方文档和stack ...
- Python中的random模块
Python中的random模块用于生成随机数.下面介绍一下random模块中最常用的几个函数. random.random random.random()用于生成一个0到1的随机符点数: 0 < ...
- 浅析Python中的struct模块
最近在学习python网络编程这一块,在写简单的socket通信代码时,遇到了struct这个模块的使用,当时不太清楚这到底有和作用,后来查阅了相关资料大概了解了,在这里做一下简单的总结. 了解c语言 ...
- python中的StringIO模块
python中的StringIO模块 标签:python StringIO 此模块主要用于在内存缓冲区中读写数据.模块是用类编写的,只有一个StringIO类,所以它的可用方法都在类中.此类中的大部分 ...
- python中的select模块
介绍: Python中的select模块专注于I/O多路复用,提供了select poll epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqu ...
- python中的shutil模块
目录 python中的shutil模块 目录和文件操作 归档操作 python中的shutil模块 shutil模块对文件和文件集合提供了许多高级操作,特别是提供了支持文件复制和删除的函数. 目录和文 ...
随机推荐
- django 调试 监控文件变化 自动刷新浏览器
问题描述:修改html js py等文件后,自动刷新浏览器,解放F5,提高效率 解决办法:使用gulp,使用bowerSync 关于gulp,可以查看系列教程 关于bowerSync,查看官网 关于结 ...
- thinkphp5.0学习笔记(四)数据库的操作
ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,我们只需要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实现,Db类会自动调用相应的数据库驱动来处理.采用PDO ...
- [leetcode-532-K-diff Pairs in an Array]
Given an array of integers and an integer k, you need to find the number of unique k-diff pairs in t ...
- RADIUS and IPv6[frc-3162译文]
如今项目中需要涉及到RADIUS及IPv6的使用,而网络中的资料相对较少,现对frc-3162进行中文翻译,分享出来. 由于英语水平有限,翻译不恰当的地方,还请提出,便于在下及时修改. 原文链接 这份 ...
- 预编译语句(Prepared Statements)介绍,以MySQL为例
背景 本文重点讲述MySQL中的预编译语句并从MySQL的Connector/J源码出发讲述其在Java语言中相关使用. 注意:文中的描述与结论基于MySQL 5.7.16以及Connect/J 5. ...
- 阿里云服务器linux(cenos)下 jdk、tomcat的安装配置
一.JDK的安装与环境配置 1. 下载jdk http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-213315 ...
- H5编辑器核心算法和思想-遁地龙卷风
代码和特性在chrome49下测试有效. 文本渲染的本质是对文本节点的渲染,通过浏览器内置的对象Range可以获得选择的起始点.与终止点 var range = getRangeObject(); ...
- 浅入深出之Java集合框架(中)
Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- JQuery中常用的选择器
属性选择器 1> [attribute] 概述:匹配包含给定属性的元素. 示例 jQuery 代码:$("div[id]") 描述:查找所有含有 id 属性的 div 元素 ...
- 互联网级监控系统必备-时序数据库之Influxdb
时间序列数据库,简称时序数据库,Time Series Database,一个全新的领域,最大的特点就是每个条数据都带有Time列. 时序数据库到底能用到什么业务场景,答案是:监控系统. Baidu一 ...