本文介绍了正则表达式的语法及Python中的正则表达式的应用。

Python中的re模块使 Python 语言拥有全部的正则表达式功能。

参考资料:Python3 re模块官方文档

什么是正则表达式

正则表达式又称规则表达式 ,许多编程语言都支持利用正则表达式进行字符串操作。Python中的re模块使 Python 语言拥有全部的正则表达式功能。

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。正则表达式是一个特殊的字符组合,用来判断字符串是否与其匹配。

举个例子:

四条腿活得 —匹配— > 牛

以A开头的四个单词 —匹配—> “Andy”

正则表达式 —匹配— > 字符串

正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它匹配了。

本文主要介绍Python中re模块的常用 函数。

Python原始字符串

正则表达式使用反斜杠字符('\')来表示特殊形式或允许使用特殊字符而不调用其特殊意义。这与Python在字符串文字中的相同目的的使用相同。例如,要匹配文字反斜杠,可能必须将'\\\\'作为正则表达式字符串,因为正则表达式必须是\\,每个反斜杠必须在普通的Python字符串文字中表示为\\

解决方案是使用Python的原始字符串符号进行正则表达式模式,在以'r'为前缀的字符串文字中,反斜杠不以任何特殊的方式处理。所以r'\n'是一个包含“\”“n”的双字符串,而“\n”是包含换行符的单字符串。

正则表达式的构成

  1. 普通字符(字符串和数字,例如:abc123)
  2. 元字符(特殊字符,例如:. ^ $ * + ? { } | ( ) \)

元字符

常用元字符表

模式 描述
^ 匹配字符串的开头。
$ 匹配字符串的结尾。
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[…] 用来表示一组字符,单独列出:[amg] 匹配 ‘a’,’m’或’g’。
[^…] 不在[]中的字符:[^abc]匹配除了a,b,c之外的字符。
\w 匹配字母数字及下划线。
\W 匹配非字母数字及下划线。
\s 匹配任意空白字符,等价于 [\t\n\r\f]。
\S 匹配任意非空字符。
\d 匹配任意数字,等价于 [0-9]。
\D 匹配任意非数字。
\A 匹配字符串开始。
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z 匹配字符串结束。
\G 匹配最后匹配完成的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ’er\b’ 可以匹配"never" 中的 ’er’,但不能匹配 “verb” 中的 ’er’。
\B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ’er’,但不能匹配 “never” 中的 ’er’。
\n,\t \n: 匹配一个换行符。\t 匹配一个制表符。
* 匹配0个或多个的表达式。
+ 匹配1个或多个的表达式。
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式。
{n} 精确匹配n个前面表达式。
{n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式。
a|b 匹配a或者b。
(…) 匹配括号内的表达式,也表示一个组。

正则表达式修饰符—可选标志位

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 or(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

修饰符 描述信息
re.A 使\w,\W,\b,\B,\d,\D,\s和\S执行仅与ASCII匹配而不是完全的Unicode匹配。这只对Unicode模式有意义,对于字节模式将被忽略。
re.DEBUG 显示有关编译表达式的调试信息。
re.I 使匹配对大小写不敏感。
re.L 做本地化识别(locale-aware)匹配。
re.M 多行匹配,影响 ^ 和 $。
re.S 使 . 匹配包括换行在内的所有字符。
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

标志位补充解释:

  • re.I(re.IGNORECASE)

    使匹配对大小写不敏感;字符类和字符串匹配字母时忽略大小写。举个例子,指定这个标识位后,[A-Z]也可以匹配小写字母。

  • re.L(re.LOCALE)

    影响 “w, “W, “b, 和 “B,这取决于当前的本地化设置。

    locales 是 C 语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。举个例子,如果你正在处理法文文本,你想用 “w+ 来匹配文字,但 “w 只匹配字符类 [A-Za-z];它并不能匹配 “é” 或 “ç”。如果你的系统配置适当且本地化设置为法语,那么内部的 C 函数将告诉程序 “é” 也应该被认为是一个字母。当在编译正则表达式时使用 LOCALE 标志会得到用这些 C 函数来处理 “w 後的编译对象;这会更慢,但也会象你希望的那样可以用 “w+ 来匹配法文文本。

  • re.M(re.MULTILINE)

    使用 “^” 只匹配字符串的开始,而 $ 则只匹配字符串的结尾和直接在换行(如果有的话)前的字符串结尾。当指定本标志后, “^” 匹配字符串的开始和字符串中每行的开始。同样的, $ 元字符匹配字符串结尾和字符串中每行的结尾(直接在每个换行之前)。

  • re.S(re.DOTALL)

    使 “.” 特殊字符完全匹配任何字符,包括换行;没有这个标志, “.” 匹配除了换行外的任何字符。

  • re.X(re.VERBOSE)

    该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标志被指定时,在 RE 字符串中的空白符被忽略,除非该空白符在字符类中或在反斜杠后面;这可以让你更清晰地组织和缩进re表达式。它也可以允许你将注释写入re表达式,这些注释会被引擎忽略;注释用 “#“号 来标识,不过该符号不能在字符串或反斜杠后面。

正则表达式实例:

实例 描述
python 匹配 “python”。
[Pp]ython 匹配 “Python” 或 “python”。
no[bd]e 匹配 “nobe” 或 “node”。
[aeiou] 匹配中括号内的任意一个字母。
[0-9] 匹配任何数字。类似于 [0123456789]。
[a-z] 匹配任何小写字母。
[A-Z] 匹配任何大写字母。
[a-zA-Z0-9] 匹配任何字母及数字。
[^aeiou] 除了aeiou字母以外的所有字符。
[^0-9] 匹配除了数字外的字符。

re模块常用方法

re.match

re.match() 尝试从字符串的起始位置匹配,如果起始位置不能匹配,re.match()就返回None。

语法:

re.match(pattern, string, flags=0)

  • pattern:匹配的正则表达式
  • string:要匹配的字符串
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等。

re.match方法匹配成功后返回一个匹配对象,否则返回None。

import re

s = "life is short, you need Python."
print(re.match(r'life', s))  # 从开始位置能成功匹配到
print(re.match(r'Life', s, re.I))  # 使用re.I标志位来指定忽略大小写
print(re.match(r'life', s).span())  # .span可以获取到具体匹配的起始位置
print(re.match(r'Python', s))  # 从开始位置不能成功匹配到

输出:

<_sre.SRE_Match object; span=(0, 4), match='life'>
<_sre.SRE_Match object; span=(0, 4), match='life'>
(0, 4)
None

对于匹配对象可以使用group(num)和groups()来获取匹配表达式。

  • group(num=0):匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。

  • groups():返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。

    import re
    
    s = "life is short, you need Python."
    ret = re.match(r'(.*) is (.*)', s)
    print("ret.group():", ret.group())
    print("ret.group(1, 2):", ret.group(1, 2))
    print("ret.group(1):", ret.group(1))
    print("ret.group(2):", ret.group(2))
    print("ret.groups():", ret.groups())
    

    输出:

    ret.group(): life is short, you need Python.
    ret.group(1, 2): ('life', 'short, you need Python.')
    ret.group(1): life
    ret.group(2): short, you need Python.
    ret.groups(): ('life', 'short, you need Python.')
    

    分组命名匹配:

    (?P<分组的名字>正则规则) **注意:**是大写的P

    import re
    
    s = "life is short, you need Python."
    ret = re.match(r'(?P<m1>.*) is (?P<m2>.*)', s)
    print("ret.group():", ret.group())
    print("ret.group('m1'):", ret.group('m1'))  # 支持使用组名访问匹配项
    print("ret.group('m2'):", ret.group('m2'))
    print("ret.group(1, 2):", ret.group(1, 2))
    print("ret.group(1):", ret.group(1))
    print("ret.group(2):", ret.group(2))
    print("ret.groups():", ret.groups())
    

    输出:

    ret.group(): life is short, you need Python.
    ret.group('m1'): life
    ret.group('m2'): short, you need Python.
    ret.group(1, 2): ('life', 'short, you need Python.')
    ret.group(1): life
    ret.group(2): short, you need Python.
    ret.groups(): ('life', 'short, you need Python.')
    

re.search

re.search 扫描整个字符串并返回第一个成功的匹配。

语法:

re.search(pattern, string, flags=0)

  • pattern:匹配的正则表达式。
  • string:要匹配的字符串。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

re.search()方法匹配成功后会返回一个匹配的对象,否则返回None。

import re

s = "life is short, you need Python."
print(re.search(r'life', s))
print(re.search(r'life', s).span())
print(re.search(r'Python', s))
print(re.search(r'Python', s).span())
print(re.search(r'xxx', s))

输出:

<_sre.SRE_Match object; span=(0, 4), match='life'>
(0, 4)
<_sre.SRE_Match object; span=(24, 30), match='Python'>
(24, 30)
None

同样可以使用group(num)groups() 来获取匹配表达式。

import re

s = "life is short, you need Python."
ret = re.search(r'(.*) is (.*)', s)
print(ret.group())
print(ret.group(1, 2))
print(ret.group(1))
print(ret.group(2))
print(ret.groups())

输出:

life is short, you need Python.
('life', 'short, you need Python.')
life
short, you need Python.
('life', 'short, you need Python.')

re.match和re.search的区别:

re.match只匹配字符串的开始,如果字符串的开始位置不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配,整个字符串都找不到匹配才返回None。

re.split

用匹配到的对象将字符串分割成列表并返回。

语法:

re.split(pattern, string, maxsplit=0, flags=0)

  • pattern:匹配的正则表达式。
  • string:要匹配的字符串。
  • maxsplit:模式匹配后分割的最大次数,默认 0 表示分割所有的匹配。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

示例1:

import re

s = "Abc123.aBc456.abC789"
ret1 = re.split(r'\.', s)  # 用.分割字符串s
ret2 = re.split(r'\.', s, 1)  # 用.分割字符串s,只分割一次
ret3 = re.split(r'[.\d]+', s)  # 用.和数字分s
print("用.分割字符串s:", ret1)
print("用.分割字符串s,只分割一次:", ret2)
print("用.和数字分s:", ret3)

输出:

.分割字符串s: ['Abc123', 'aBc456', 'abC789']
.分割字符串s只分割一次: ['Abc123', 'aBc456.abC789']
.和数字分s: ['Abc', 'aBc', 'abC', '']

注意:

当正则匹配项位于字符串的开头或结尾时,re.split()得到列表会有空元素。

re.findall

找到字符串中所有的匹配项,以列表的形式返回。

语法:

re.findall(pattern, string, flags=0)

  • pattern:匹配的正则表达式。
  • string:要匹配的字符串。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

示例:

import re

s = "Abc123.aBc456.abC789"
ret = re.findall(r'\d+', s)    # 找到所有的连续数字,并以列表形式返回
print("所有的数字:", ret)

输出:

所有的数字 ['123', '456', '789']

re.sub

re.sub用来替换字符串中的匹配项。

语法:

re.sub(pattern, repl, string, count=0, flags=0)

  • pattern:匹配的正则表达式。
  • repl:替换的字符串,也可为一个函数。
  • string:要被查找替换的原始字符串。
  • count:模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

示例1:

import re

s = "life is short, you need Python.  # 人生苦短,我用Python。"

# 删除#及后面的内容(把#及后面的字符替换成空字符串)
s1 = re.sub(r'#.*$', "", s)
print("去掉注释后:", s1)

输出:

去掉注释后 life is short, you need Python.

示例2(当repl参数是一个函数时):

import re


# 将匹配的数字乘于2
def double(matched):
    num = int(matched.group("num"))  # 注意要转成int类型
    return str(num * 2)  # 将乘以2后的结果转成str类型

s = "1 + 2 = 3"
s1 = re.sub(r'(?P<num>\d+)', double, s)  # 分组命名匹配
print("re.sub替换后:", s1)

输出:

re.sub替换后 2 + 4 = 6

re.compile

将正则表达式模式编译为正则表达式对象,可用于使用其match()search()方法进行匹配,如下所述。

re_obj = re.compile(pattern)
ret = re_obj.match(string)

等价于:

ret = re.match(pattern, string)

正则表达式练习

练习一

匹配手机号

ret = re.match(r'(1)[3578]\d{9}', s)

练习二

匹配邮箱

ret = re.match(r'^([a-z.0-9]{1,26})@([a-z.0-9]{1,20})(.[a-z0-9]{1,8})$', s)

练习三

匹配IP

ret = re.match(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$', s)

扫码关注微信公众号