引言

刚接触正则表达式,我也曾被它们天书似的符号组合给吓住,但经过一段时间的深入学习,发现它并没有想象中那么可怕,只要多实践,多理解,也是可以轻松搞定的。

而且我发现带着问题去学习,求知欲会驱使着你往前走,不知不觉就懂了。

下面就是我在学习中提出的几个问题,在后面会依次进行讨论。由于正则表达式涉及到的内容确实非常多,分成两篇来阐述。

  1. 什么是正则表达式?
  2. 正则表达式可以干什么?
  3. 正则表达式的语法以及在 python 中这些语法是如何使用的?
  4. 正则表达式如何处理中文字符?
  5. python 的正则表达式库中有哪些重要的函数?

什么是正则表达式?

正则表达式使用单个字符串来描述,匹配一系列符合某个句法规则的字符串。

— 维基百科

先来划重点:

  1. 正则表达式的表现形式是 单个字符串
  2. 它用来执行匹配的动作
  3. 匹配的对象也是字符串

语言总是有些苍白的,必须要结合实例才能理解的更清楚,先来看一个例子:

>>> 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='@'>
  • \un Unicode转义字符序列。其中n是一个用四个十六进制数字表示的Unicode字符

    >>> re.search(r'\u00A9','©')
    
    <re.Match object; span=(0, 1), match='©'>

小结

如果你真的读完了这些实例,我敢说你对正则表达式会有一定的理解了吧。下篇会重点讲解python 中的正则表达式库函数,对中文的处理等,敬请期待~

参考文档

  1. 维基百科—正则表达式

系列文章列表

python 历险记(六)— python 对正则表达式的使用(上篇)的更多相关文章

  1. Python学习(六) Python数据类型:字典(重要)

    字典dict: 字典其实就相当于java里面的Map,用来存储键值对的.其中存储的数据时无序的. 假如有这样的数据: t1=['name','age','sex'] t2=['tom',30,'mal ...

  2. Python基础(六) python生成xml测试报告

    思路: 1.使用xslt样式,这样可以很好的和xml结合,做出漂亮的报告 2.生成xml结构 xslt样式是个很有意思,也很强大的,现在用的很多,很方便就能做出一个漂亮的报告,可以百度一下,语法相当简 ...

  3. python 教程 第十六章、 正则表达式

    第十六章. 正则表达式 1)    匹配多个表达式 记号  re1|re2 说明  匹配正则表达式re1或re2 举例  foo|bar  匹配  foo, bar 记号  {N} 说明  匹配前面出 ...

  4. python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处? 什么是 python 中的模块? 引入模块有几种方式? 模块的查找顺序 模块中包含执行语句的情况 用 dir() 函数来窥探模块 python 的 ...

  5. 孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5

    孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5并学习权限设置 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十二天. 今天继续学习mongo ...

  6. 孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4

    孤荷凌寒自学python第六十五天学习mongoDB的基本操作并进行简单封装4 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十一天. 今天继续学习mongoDB的简单操作 ...

  7. 孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3

    孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十天. 今天继续学习mongoDB的简单操作, ...

  8. 孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2

    孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第九天. 今天继续学习mongoDB的简单操作, ...

  9. python re 模块和基础正则表达式

    1.迭代器:对象在其内部实现了iter(),__iter__()方法,可以用next方法实现自我遍历. 二.python正则表达式 1.python通过re模块支持正则表达式 2.查看当前系统有哪些p ...

随机推荐

  1. DOM LEVEL 1 中的那些事儿[总结篇-上]

    DOM是前端编程中一个非常重要的部分,我们在动态修改页面的样式.内容.添加页面动画以及为页面元素绑定事件时,本质都是在操作DOM.DOM并不是JS语言的一个部分,我们通过JAVA.PHP等语言抓取网页 ...

  2. 【BZOJ3097】 Hash Killer I

    BZOJ3097 Hash Killer I Solution 考虑它是自然溢出,相当于就是对\(2^{63}\)取膜 那么就有\(aaaaa...aaa\)(多于64个)和\(baaaa...aaa ...

  3. [学习笔记]普通平衡树Splay

    哈哈哈哈哈哈哈终于会打\(splay\)啦 现在我来发一下\(splay\)的讲解吧 小蒟蒻由于码风与他人不同,所以自己找了上百篇码风诡异的\(splay\)合成的,感谢\(zcysky\)的代码与我 ...

  4. Android逆向——smali复杂类解析

    i春秋作家:HAI_ 之前在Android逆向——初识smali与java类中讲解了基本的HelloWorld和简单类.这节课就要进一步深入.如果能够耐下心来分析一定会有所收获.——写给自己和后来人. ...

  5. Java Listener中Spring接口注入的使用

    在项目中使用Spring通常使用他的依赖注入可以很好的处理,接口与实现类之间的耦合性,但是通常的应用场景中都是Service层和DAO层,或者web层的话, 也是与Strust2来整合,那么如何在Li ...

  6. [vuejs] 终端npm run dev 不能自动打开浏览器运行项目解决办法

    终端执行: npm run dev 出现: I Your application is running here: http://localhost:8080 但并没有打开浏览器运行项目 解决办法: ...

  7. ie6兼容性处理

    IE6下border-bottom不起作用? 在IE6下,border-bottom:1px solid #000 不起作用,但border:1px solid #000 其作用. (经过测试,对于b ...

  8. Windows Phone开发手记-WinRT下自定义圆形ItemsControl

    这里的ItemsControl指的是Xaml里的集合控件,包括ListView,GridView等,此篇博客主要参考MSDN Blog的一篇文章,具体出处为:http://blogs.msdn.com ...

  9. Django中安装搜索引擎方法。

    全文检索 全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理. haystack:全文检索的框架,支持whoosh.solr.Xapian.Elasticsearc ...

  10. Flask常用的钩子函数

    before_first_request:处理第一次请求之前执行.例如以下代码: @app.before_first_request def first_request(): print 'first ...