引言

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

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

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

  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. Unity相机跟随-----根据速度设置偏移量

    这里假设在水中的船,船有惯性,在不添加前进动力的情况下会继续移动,但是船身是可以360度自由旋转,当船的运动速度在船的前方的时候,相机会根据向前的速度的大小,设置相机的偏移量,从而提高游戏的动态带感. ...

  2. Weekly Contest 133

    1030. Matrix Cells in Distance Order We are given a matrix with R rows and C columns has cells with ...

  3. 阿里云ECS 介绍

    1.阿里云产品概述 1 2.阿里云基础架构介绍 2 3. ECS产品概念和功能 6 4. ECS运维管理和API 12 1.阿里云产品概述 2.阿里云基础架构介绍 ECS 主要有五个主要的组成部分 作 ...

  4. SpringCloud之Fegin

    Fegin是一个声明似的web服务客户端,它使得编写web服务客户端变得更加容易.使用Fegin创建一个接口并对它进行注解.它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持 ...

  5. [ActionScript 3.0] as3处理xml的功能和遍历节点

    as3比as2处理xml的功能增强了N倍,获取或遍历节点非常之方便,类似于json对像的处理方式. XML 的一个强大功能是它能够通过文本字符的线性字符串提供复杂的嵌套数据.将数据加载到 XML 对象 ...

  6. iOS开发-实现相机app的方法[转载自官方]

    This brief code example to illustrates how you can capture video and convert the frames you get to U ...

  7. 开发ASP.NET MVC 在线录音录像(音视频录制并上传)

    最近有个在线招聘录音的开发需求,需要在招聘网站上让招聘者上传录音和视频. 找到两个不错的javascript开源,可以在除了IE以外的浏览器运行. https://github.com/mattdia ...

  8. 安装SVN并进行汉化的详细步骤

    安装SVN并进行汉化的详细步骤 SAE提供了不同的代码部署方式,可以分为两类:一是通过SVN客户端部署,这是SAE推荐的代码部署方法.另一个是通过非SVN客户端部署,即在线代码在线编辑器和推荐应用安装 ...

  9. CSS3实现纸张边角卷起效果

    html代码 <body> <div class="page"> <div class="page-box"> <h1 ...

  10. windows系统搭建禅道系统(BUG管理工具)

    我也呆过三家公司了,用过的BUG管理工具也是五花八门的,常见的一般有禅道,bugzilla,jira等 个人比较推荐禅道,功能强大,主页的说明文档也是相当详细,最主要的是,用的人比较多,出现使用问题一 ...