引言

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

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

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

  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. 【转】ABP使用Mysql数据库

    原文地址:https://www.cnblogs.com/LonelyCode/p/6477065.html 1.先安装Mysql的包,EntityFramework和Web项目都需要安装 2.修改W ...

  2. Linux的1000个命令

    目录 Linux常用命令 uptime wget uname free who last history pwd cd ls cat head tail tr wc cut diff touch mk ...

  3. 【AWK】:常用总结

    单机文本数据处理,常用AWK,总结一下AWK最常用的要点,备忘备查. 1.What is AWK(1)Aho.Weinberger.Kernighan三位发明者名字首字母:(2)一个行文本处理工具: ...

  4. javaScript中BOM

    BOM是browser object model的缩写,简称浏览器对象模型 主要处理浏览器窗口(window)和框架(iframe),简述了与浏览器进行交互的方法和接口, 可以对浏览器窗口进行访问和操 ...

  5. VM虚拟机-Ubuntu server- 桥接模式网络配置

    问题描述: 在Ubuntu虚拟机安装完毕之后,网络连接采用桥接模式,对虚拟机的eth0网卡进行静态IP设置,参数配置没有问题,但网络一直没有连接成功:即:无法实现本地主机连通虚拟机(ping不通该虚拟 ...

  6. 多线程的实现及常用方法_DAY23

    1:多线程(理解) (1)如果一个应用程序有多条执行路径,则被称为多线程程序. 进程:正在执行的程序. 线程:程序的执行路径,执行单元. 单线程:如果一个应用程序只有一条执行路径,则被称为单线程程序. ...

  7. c++程序时间统计

    如下所示,引入<time.h>我们就可以统计时间了: #include<iostream> #include<time.h> #include<windows ...

  8. ElasticSearch入门3: Spring Boot集成ElasticSearch

    第一步:创建项目elasticsearch 编写pom文件 <?xml version="1.0" encoding="UTF-8"?> <p ...

  9. Java抽象类应用—模板方法模式

    模板方法模式(Templete method) 定义一个操作中的算法的骨架,而将一些可变部分的实现延迟到子类中,模板方法模式使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤. 例: ...

  10. Redis学习系列四Hash(字典)

    一.简介 Redis中的Hash字典相当于C#中的Hashtable,是一种无序字典,内存存储了很对的键值对,实现上和Hashtable一样,都是"数组+链表"二维结构,都是对关键 ...