python 历险记(六)— python 对正则表达式的使用(上篇)
引言
刚接触正则表达式,我也曾被它们天书似的符号组合给吓住,但经过一段时间的深入学习,发现它并没有想象中那么可怕,只要多实践,多理解,也是可以轻松搞定的。
而且我发现带着问题去学习,求知欲会驱使着你往前走,不知不觉就懂了。
下面就是我在学习中提出的几个问题,在后面会依次进行讨论。由于正则表达式涉及到的内容确实非常多,分成两篇来阐述。
- 什么是正则表达式?
- 正则表达式可以干什么?
- 正则表达式的语法以及在 python 中这些语法是如何使用的?
- 正则表达式如何处理中文字符?
- python 的正则表达式库中有哪些重要的函数?
什么是正则表达式?
正则表达式使用单个字符串来描述,匹配一系列符合某个句法规则的字符串。
— 维基百科
先来划重点:
- 正则表达式的表现形式是 单个字符串
- 它用来执行匹配的动作
- 匹配的对象也是字符串
语言总是有些苍白的,必须要结合实例才能理解的更清楚,先来看一个例子:
>>> import re
>>>re.search(r'wo\w+d', 'hello world!')
<re.Match object; span=(6, 11), match='world'>
>>>
这里先概略说明 re.search 方法:引入的 re 模块就是 python 的正则表达式模块,re.search 函数目的就是接受一个正则表达式和一个字符串,并以 Match 对象的形式返回匹配的第一个元素。如果没有匹配到,则会返回 None。(关于 search 函数先了解这些就可以,后面会有详细讲解。)
下面就拿这个示例中 re.search 中的参数来匹配下上面的概念,加深一下理解
- 'wo\w+d' 就是正则表达式,它还有一个名称叫做_模式(pattern)_ ,表示
wo字母后有多个字母并一直到d字母出现为止(现在不明白没关系,只要知道它就是正则表达式就可以了,后面会详细讲) - 在 'wo\w+d' 前面还有一个
r表示什么呢?这个r表示 raw的意思,就是原始字符串。原始字符串不会将\解释成一个转义字符,而是这样做对正则表达式好处是大大的,只有这样\w才能起作用。 - 'hello world!' 就是要匹配的字符串。
- 整个函数就表示从 'hello world!' 字符串中搜索出符合_'wo\w+d'_ 模式的字符串,并展示出来,于是
world字符串就被筛选了出来。
正则表达式有什么用?
我们学习正则表达式的目的是什么?当然是为了有朝一日能使用它解决我们面临的问题,要不然,学它干嘛。那我们就来聊聊正则表达式的用途:
字符串验证
你肯定在网页上注册过账号吧,假如你在注册 github 网站,它要求你输入 Email,而你却胡乱填写了几个数字就想注册,这时就会弹出提示 "Email is invalid",意思就是你的邮箱是无效的,这就是正则表达式的功劳。
替换文本
假如你正在写一篇关于 java 的文章,写着写着,你觉得换成 python 更好些,你想一下把出现 java , Java 的地方全都替换成 python , 正则表达式可以帮你做到。
从字符串中提取出要获取的字符串
假如你正在爬取一个汽车排行榜页面,想要获取每个车型的编号,而车型编号则隐藏在链接中,怎么获取呢?用正则表达式可以。
正则表达式的语法及使用实例
对刚接触的同学来说,正则表达式的语法很晦涩。不用担心,我们先大致浏览一下完整的语法组合,后面再使用前面讲过的 re.search 方法一个个详细介绍,讲着讲着,我相信你就明白了。
正则表达式语法有哪些?
| 字符 | 功能描述 |
|---|---|
\ |
特殊字符转义 |
^ |
匹配字符串的开始位置 |
$ |
匹配字符串的结束位置 |
* |
匹配前面的子表达式零次或多次 |
+ |
匹配前面的子表达式一次或多次 |
? |
匹配前面的子表达式零次或一次 |
{n} |
n是非负整数,匹配n次 |
{n,} |
n 是非负整数,匹配 >=n 次 |
{n,m} |
m是非负整数,n<=m, 匹配>= n 并且 <=m 次 |
? |
非贪心量化 |
. |
匹配除“\r”“\n”之外的任何单个字符 |
(pattern) |
匹配pattern并获取这一匹配的子字符串 |
(?:pattern) |
非获取匹配 |
(?=pattern) |
正向肯定预查 |
(?!pattern) |
正向否定预查 |
(?<=pattern) |
反向(look behind)肯定预查 |
(?<!pattern) |
反向否定预查 |
x|y |
没有包围在()里,其范围是整个正则表达式 |
[xyz] |
字符集合(character class),匹配所包含的任意一个字符 |
[^xyz] |
排除型字符集合(negated character classes),匹配未列出的任意字符 |
[a-z] |
字符范围,匹配指定范围内的任意字符 |
[^a-z] |
排除型的字符范围,匹配任何不在指定范围内的任意字符 |
\b |
匹配一个单词边界,也就是指单词和空格间的位置 |
\B |
匹配非单词边界 |
\cx |
匹配由x指明的控制字符 |
\d |
匹配一个数字字符。等价于[0-9] |
\D |
匹配一个非数字字符。等价于[^0-9]。 |
\f |
匹配一个换页符。等价于\x0c和\cL。 |
\n |
匹配一个换行符。等价于\x0a和\cJ。 |
\r |
匹配一个回车符。等价于\x0d和\cM。 |
\s |
匹配任何空白字符。等价于[ \f\n\r\t\v] |
\S |
匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
\t |
匹配一个制表符。等价于\x09和\cI。 |
\v |
匹配一个垂直制表符。等价于\x0b和\cK。 |
\w |
匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]” |
\W |
匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。 |
\ck |
匹配控制转义字符。k代表一个字符。等价于“Ctrl-k” |
\xnn |
十六进制转义字符序列 |
\n |
标识一个八进制转义值或一个向后引用 |
\un |
Unicode转义字符序列 |
这些正则到底该怎么用?
浏览一遍,感觉怎么样,是不是摩拳擦掌,想要立刻实践一番,嘿嘿。好的我们现在就开干。
^匹配字符串的开始位置>>> import re
>>> re.search(r'^h', 'he is a hero!')
<re.Match object; span=(0, 1), match='h'>
这个例子中虽然有两个 h,因为前面有
^所以只会匹配第一个$匹配字符串的结束位置>>> import re >>> re.search(r't$','this is an object') <re.Match object; span=(16, 17), match='t'>
虽然这个句子前后都有 t,却是最后的被匹配到了
*匹配前面的子表达式 0 次或多次,例如,"zo*" 能匹配"z","zo","zoo",我们来验证下>>> import re >>> re.search(r'zo*', 'z') <re.Match object; span=(0, 1), match='z'>
>>> re.search(r'zo*', 'zo') <re.Match object; span=(0, 2), match='zo'>
>>> re.search(r'zo*', 'zoo') <re.Match object; span=(0, 3), match='zoo'>
这里
*还有一种写法 {0,},两者等价。其中{}叫做重复。来看例子。import re
re.search(r'zo{0,}','z')
<_sre.SRE_Match object; span=(0, 1), match='z'>
re.search(r'zo{0,}','zo')
<_sre.SRE_Match object; span=(0, 2), match='zo'>
re.search(r'zo{0,}','zoo')
<_sre.SRE_Match object; span=(0, 3), match='zoo'>
+匹配前面的子表达式一次或多次,以 "zo+" 为例,它能匹配 "zo","zoo",来验证下>>> import re
>>> re.search(r'zo+', 'zo') <re.Match object; span=(0, 2), match='zo'>
>>> re.search(r'zo+', 'zoo') <re.Match object; span=(0, 3), match='zoo'>
这里
+还有一种写法{1,}两者是等价的,来看例子。>>> import re >>> re.search(r'zo{1,}','zo') <re.Match object; span=(0, 2), match='zo'> >>> re.search(r'zo{1,}','zoo') <re.Match object; span=(0, 3), match='zoo'>
?匹配前面的子表达式0次或 1次,以 "ab(cd)?" 为例,可以匹配 "ab","abcd",看下面例子import re
re.search(r'ab(cd)?','ab')
<_sre.SRE_Match object; span=(0, 2), match='ab'>
re.search(r'ab(cd)?','abcd')
<_sre.SRE_Match object; span=(0, 4), match='abcd'>
这里
?还有一种写法{0,1}两者等价,看下面import re
re.search(r'ab(cd){0,1}', 'ab')
<_sre.SRE_Match object; span=(0, 2), match='ab'>
re.search(r'ab(cd){0,1}', 'abcd')
<_sre.SRE_Match object; span=(0, 4), match='abcd'>
{n}n 必须是非负整数,能匹配确定的 n 次,以 "o{2}" 为例,它能匹配 "good", 却不能匹配 "god"import re
re.search(r'o{2}', 'god')
re.search(r'o{2}', 'good')
<_sre.SRE_Match object; span=(1, 3), match='oo'>
{n,}n是一个非负整数。至少能匹配 n次。例如,“o{2,}”不能匹配 “god”中的 “o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”,这个可看前面示例。{n,m}m和n均为非负整数,其中n<=m。例如,“o{1,2}”将匹配“google”中的两个o。“o{0,1}”等价于“o?”。注意在逗号和两个数之间不能有空格re.search(r'o{1,2}', 'google')
<_sre.SRE_Match object; span=(1, 3), match='oo'>
?这个叫做非贪心量化(Non-greedy quantifiers),这个字符和前面的?有什么区别?应用场合是什么呢?当该字符紧跟在任何一个其他重复修饰符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。举个例子,"o+" 默认会匹配 o 一次或多次,如果在后面加上 "?",则匹配一次。来看代码。
re.search(r'o+?', 'google')
<_sre.SRE_Match object; span=(1, 2), match='o'>
re.search(r'o+', 'google')
<_sre.SRE_Match object; span=(1, 3), match='oo'>
.匹配除了\r,\n之外的任何单个字符,要匹配包括“\r”“\n”在内的任何字符,请使用像“(.|\r|\n)”的模式import re
re.search(r'a.', 'ab')
<_sre.SRE_Match object; span=(0, 2), match='ab'>
(pattern)匹配 pattern 并获取这一匹配的子字符串,并用于向后引用。使用圆括号可以指定分组。当使用分组时,除了获取到整个匹配的完整字符串,也可以从匹配中选择每个单独的分组。下面给出一个本地电话号码的示例,其中每个括号内匹配的数字都是一个分组。
>>> import re
>>> match = re.search(r'([\d]{3,4})-([\d]{7,8})', '010-12345678')
>>> match
<re.Match object; span=(0, 12), match='010-12345678'>
>>> match.group(1)
'010'
>>> match.group(2)
'12345678'
>>> match.group()
'010-12345678'
>>> match.groups()
('010', '12345678')
前面我们只是简单介绍了
match对象,为了深入的理解分组,这里还要简单介绍下该对象的几个方法以及如何对应分组信息的:groups()用于返回一个对应每一个单个分组的元组。>>> match.groups()
('010', '12345678')
group()方法(不含参数)则返回完整的匹配字符串>>> match.group()
'010-12345678'
group(num)num 是分组编号,按照分组顺序,从 1 开始取值,能获取具体的分组数据。>>> match.group(1)
'010'
>>> match.group(2)
'12345678'
(?:pattern)匹配 pattern 但不获取匹配的子字符串(shy groups),也就是说这是一个非获取匹配,不存储匹配的子字符串用于向后引用。这种格式的圆括号不会作为分组信息,只用于匹配,即在python 调用search方法而得到的match对象不会将圆括号作为分组存储起来。来看下面例子,只获取电话号,而不获取地区区号。
>>> match = re.search(r'(?:[\d]{3,4})-([\d]{7,8})', '010-12345678')
>>> match.groups()
('12345678',)
>>> match.group()
'010-12345678'
这种形式对使用 或 字符`(|)”来组合一个模式的各个部分是很有用的,来看一个例子,想要同时匹配 city 和 cities (复数形式),就可以这样干
>>> match = re.search(r'cit(?:y|ies)','cities')
>>> match
<re.Match object; span=(0, 6), match='cities'>
>>> match = re.search(r'cit(?:y|ies)','city')
>>> match
<re.Match object; span=(0, 4), match='city'>
(?=pattern)正向肯定预查(look ahead positive assert),在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。举个例子,假设我要获取从不同 python 版本中只获取 "python" 字符串,就可以这样写:
>>> match = re.search(r'python(?=2.7|3.5|3.6|3.7)', 'python3.7')
>>> match
<re.Match object; span=(0, 6), match='python'>
预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。那么在 python 版本后再加上其他信息,整体就无法匹配了。
看下面例子,得到的结果只能是 null。
>>> match = re.search(r'python(?=2.7|3.5|3.6|3.7) is hacking!', 'python3.7 is hacking!')
>>> match
(?!pattern)正向否定预查(negative assert),看名字也知道是 正向肯定预查的反面。在任何不匹配 pattern 的字符串开始处匹配查找字符串。是一个非获取匹配,而且预查不消耗字符。看下面例子,和正向肯定预查一对比就明白了。
>>> match = re.search(r'python(?!2.7|3.5|3.6|3.7)', 'python3.7')
>>> match
>>> match = re.search(r'python(?!2.7|3.5|3.6|3.7)', 'python3.1')
>>> match
<re.Match object; span=(0, 6), match='python'>
>>> match = re.search(r'python(?!2.7|3.5|3.6|3.7) is hacking!', 'python3.1 is hacking!')
>>> match
(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。x|y或,分两种情况:没有没括号包围,范围则是整个表达式;被括号包围,返回是括号内。>>> match = re.search(r'today is sunday|tommory is monday','tommory is monday')
>>> match
<re.Match object; span=(0, 17), match='tommory is monday'>
[xyz]字符集合,匹配所包含的任意一个字符。分为下面情况普通字符
>>> match = re.search(r'[Pp]ython','python')
>>> match
<_sre.SRE_Match object; span=(0, 6), match='python'>
特殊字符仅有反斜线
\保持特殊含义,用于转义字符其它特殊字符如星号、加号、各种括号等均作为普通字符
>>> match = re.search(r'[*?+()]python','*python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='*python'>
>>> match = re.search(r'[*?+()]python','+python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='+python'>
>>> match = re.search(r'[*?+()]python','(python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='(python'>
^出现在字符串中间和末尾仅作为普通字符,出现在最前面后面会讲。>>> match = re.search(r'[*^{]python','^python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='^python'>
>>> match = re.search(r'[*^]python','^python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='^python'>
-出现在字符集合首位和末尾,仅作为普通字符,出现在中间是字符范围描述,后面会讲。>>> match = re.search(r'[-^]python','-python')
>>> match
<_sre.SRE_Match object; span=(0, 7), match='-python'>
[^xyz]排除型字符集合(negated character classes)。匹配未列出的任意字符>>> re.search(r'[^abc]def','edef') <re.Match object; span=(0, 4), match='edef'>
[a-z]字符范围。匹配指定范围内的任意字符。>>> re.search(r'[a-g]bcd','ebcd') <re.Match object; span=(0, 4), match='ebcd'>
>>> re.search(r'[a-g]bcd','hbcd')
[^a-z]排除型的字符范围。匹配任何不在指定范围内的任意字符。>>> re.search(r'[^a-g]bcd','hbcd') <re.Match object; span=(0, 4), match='hbcd'>
\b匹配一个单词边界,也就是指单词和空格间的位置。>>> re.search(r'an\b apple','an apple') <re.Match object; span=(0, 8), match='an apple'>
\B匹配非单词边界>>> re.search(r'er\B','verb') <re.Match object; span=(1, 3), match='er'>
\d匹配一个数字字符。等价于[0-9]>>> re.search(r'\d apples', '3 apples') <re.Match object; span=(0, 8), match='3 apples'>
\D匹配一个非数字字符。等价于[^0-9]>>> re.search(r'\D dog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。>>> re.search(r'a\sdog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\S匹配任何非空白字符。等价于[^ \f\n\r\t\v]>>> re.search(r'\S dog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”>>> re.search(r'\w', 'h') <re.Match object; span=(0, 1), match='h'>
\W匹配任何非单词字符。等价于“[^A-Za-z0-9_]”>>> re.search(r'\W', '@') <re.Match object; span=(0, 1), match='@'>
\unUnicode转义字符序列。其中n是一个用四个十六进制数字表示的Unicode字符>>> re.search(r'\u00A9','©') <re.Match object; span=(0, 1), match='©'>
小结
如果你真的读完了这些实例,我敢说你对正则表达式会有一定的理解了吧。下篇会重点讲解python 中的正则表达式库函数,对中文的处理等,敬请期待~
参考文档
系列文章列表
- python 历险记(一)—String,集合(List,元组,Dict)
- python 历险记(二)— python 的面向对象
- python 历险记(三)— python 的常用文件操作
- python 历险记(四)— python 中常用的 json 操作
- python 历险记(五)— python 中的模块
python 历险记(六)— python 对正则表达式的使用(上篇)的更多相关文章
- Python学习(六) Python数据类型:字典(重要)
字典dict: 字典其实就相当于java里面的Map,用来存储键值对的.其中存储的数据时无序的. 假如有这样的数据: t1=['name','age','sex'] t2=['tom',30,'mal ...
- Python基础(六) python生成xml测试报告
思路: 1.使用xslt样式,这样可以很好的和xml结合,做出漂亮的报告 2.生成xml结构 xslt样式是个很有意思,也很强大的,现在用的很多,很方便就能做出一个漂亮的报告,可以百度一下,语法相当简 ...
- python 教程 第十六章、 正则表达式
第十六章. 正则表达式 1) 匹配多个表达式 记号 re1|re2 说明 匹配正则表达式re1或re2 举例 foo|bar 匹配 foo, bar 记号 {N} 说明 匹配前面出 ...
- python 历险记(五)— python 中的模块
目录 前言 基础 模块化程序设计 模块化有哪些好处? 什么是 python 中的模块? 引入模块有几种方式? 模块的查找顺序 模块中包含执行语句的情况 用 dir() 函数来窥探模块 python 的 ...
- 孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5
孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5并学习权限设置 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十二天. 今天继续学习mongo ...
- 孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4
孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十一天. 今天继续学习mongoDB的简单操作 ...
- 孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3
孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十天. 今天继续学习mongoDB的简单操作, ...
- 孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2
孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第九天. 今天继续学习mongoDB的简单操作, ...
- python re 模块和基础正则表达式
1.迭代器:对象在其内部实现了iter(),__iter__()方法,可以用next方法实现自我遍历. 二.python正则表达式 1.python通过re模块支持正则表达式 2.查看当前系统有哪些p ...
随机推荐
- DOM LEVEL 1 中的那些事儿[总结篇-上]
DOM是前端编程中一个非常重要的部分,我们在动态修改页面的样式.内容.添加页面动画以及为页面元素绑定事件时,本质都是在操作DOM.DOM并不是JS语言的一个部分,我们通过JAVA.PHP等语言抓取网页 ...
- 【BZOJ3097】 Hash Killer I
BZOJ3097 Hash Killer I Solution 考虑它是自然溢出,相当于就是对\(2^{63}\)取膜 那么就有\(aaaaa...aaa\)(多于64个)和\(baaaa...aaa ...
- [学习笔记]普通平衡树Splay
哈哈哈哈哈哈哈终于会打\(splay\)啦 现在我来发一下\(splay\)的讲解吧 小蒟蒻由于码风与他人不同,所以自己找了上百篇码风诡异的\(splay\)合成的,感谢\(zcysky\)的代码与我 ...
- Android逆向——smali复杂类解析
i春秋作家:HAI_ 之前在Android逆向——初识smali与java类中讲解了基本的HelloWorld和简单类.这节课就要进一步深入.如果能够耐下心来分析一定会有所收获.——写给自己和后来人. ...
- Java Listener中Spring接口注入的使用
在项目中使用Spring通常使用他的依赖注入可以很好的处理,接口与实现类之间的耦合性,但是通常的应用场景中都是Service层和DAO层,或者web层的话, 也是与Strust2来整合,那么如何在Li ...
- [vuejs] 终端npm run dev 不能自动打开浏览器运行项目解决办法
终端执行: npm run dev 出现: I Your application is running here: http://localhost:8080 但并没有打开浏览器运行项目 解决办法: ...
- ie6兼容性处理
IE6下border-bottom不起作用? 在IE6下,border-bottom:1px solid #000 不起作用,但border:1px solid #000 其作用. (经过测试,对于b ...
- Windows Phone开发手记-WinRT下自定义圆形ItemsControl
这里的ItemsControl指的是Xaml里的集合控件,包括ListView,GridView等,此篇博客主要参考MSDN Blog的一篇文章,具体出处为:http://blogs.msdn.com ...
- Django中安装搜索引擎方法。
全文检索 全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理. haystack:全文检索的框架,支持whoosh.solr.Xapian.Elasticsearc ...
- Flask常用的钩子函数
before_first_request:处理第一次请求之前执行.例如以下代码: @app.before_first_request def first_request(): print 'first ...