正则表达式也叫做REs, regex, 或者regex patterns. 通常正则表达式中出现的任意一个字符代表匹配和他们一样的字符, 但正则表达式也提供了额外的符号来表达更加复杂的含义.

基本符号

以下符号相当于各种编程语言的关键字, 它们并不匹配他们本身

1
. ^ $ * + ? { } [ ] \ | ( )

上述转义字符前加上\可以去除之后的一个字符的转义, 例如 \] 匹配字符 ]

字符类

  • 在 “[“ 和 “]” 中的若干字符构成一个字符类(character class)
  • 一个字符类表示, 此位置可以匹配这个类中的任意一个字符
  • 可以使用-来表示一个范围, 例如[a-c]表示[abc]
  • 在字符类中的特殊符号不被转义

反向匹配

  • 在字符类中, 如果以^开头, 则表示匹配除此字符类中提及的任何其他字符
  • 例如[^5]匹配任何不是5的字符

转义字符

字符 解释 等价正则表达式
\d 匹配任意数字 [0-9]
\D 匹配任意的非数字 [^0-9]
\s 匹配任意空白字符 [ \t\n\r\f\v](第一个是空格)
\S 匹配任意非空白字符 [^ \t\n\r\f\v]
\w 匹配任意字符数字(alphanumeric) [a-zA-Z0-9_](最后一个是下划线)
\W 匹配任意非字符数字 [^a-zA-Z0-9_]

说明:这些字符可以和其他字符一样出现在任何合法的地方

高级的转义字符

符号 效果
| 相当于或, A|B表示此处可以匹配A或者B
^ 匹配字符串开头
$ 匹配字符串结尾
\A 强制匹配字符串开头, 无视MULTILINE标记
\Z 强制匹配字符串结尾, 无视MULTILINE标记
\b 匹配单词边界, 即非字母数字的任意其他字符
\B 匹配非单词边界字符, \b的反义

使用重复功能

符号 效果
* 使此符号之前的字符重复0到多次
+ 使此符号之前的字符重复1到多次
? 使此符号之前的字符重复0到1次
{m,n} 使此符号之前的字符重复至少m次, 至多n次(含n)

说明:这些字符都是采用贪婪匹配, 即尽可能多的重复, 从而匹配到最长的符合要求的字符串,如果想要最短匹配们可以在相应的符号后加上一个?,例如*?, +?

Python和正则表达式

1
2
import re
p = re.compile("ab+")

在Python中使用正则表达式需要两步, 即

  1. 导入re库
  2. 编译正则表达式

使用原生字符串

在正则表达式中, 使用\表示转义, 而在Python中碰巧也使用同样的符号表示转义,因此在正则表达式中, 如果需要使用\, 在Python的字符串中, 就要输入两个\, 即\\

为了避免输入太多\导致正则表达式难以理解, 可以使用Python的原生字符串, 即在字符串开头加上r,例如r"\sda",在这个字符串中, 所有字符都是原来的字符, Python不进行任何转义

Re库主要函数功能

函数 说明
re.compile() 将一个字符串编译成正则表达式
re.search() 在一个字符串中搜索匹配的正则表达式的第一个位置, 返回match对象
re.match() 在一个字符串中强制从开始位置起匹配正则表达式, 返回match对象
re.findall() 搜索字符串, 以列表类型返回全部能匹配的子串
re.split() 将一个字符串按照正则表达式匹配结果进行分割, 返回列表类型
re.finditer() 搜索字符串中全部匹配结果, 返回一个迭代类型, 每个迭代元素是match对象
re.sub() 在一个字符串中替换所有匹配正则表达式的子串, 返回替换后的字符串
  • regex = re.compile(pattern,flags=0)
    • pattern:正则表达式的字符串或者原生字符串
    • flags:正则表达式的控制标记
  • re.search(pattern,string,flags=0)
    • string:待匹配字符串
    • flags:控制标记
  • re.match(pattern,string,flags=0)
    • 参数定义相同
  • re.spilt(pattern,string,maxsplit=0,flags=0)
    • 增加一个新的参数, 表示最大分割数, 其余部分作为最后一个元素输出
  • re.finditer(pattern,string,flags=0)
    • 参数定义相同, 返回迭代结果, 从而可以用 for in的形式遍历
  • re.sub(pattern,repl,string,conut=0,flag=0)
    • repl:需要匹配替换的字符串
    • count:替换的最大次数

上述返回Match对象的函数如果没有匹配, 则返回None, 从而可以直接使用if语句进行判断是否匹配. 上述标记, 如果在编译字符串的时候就指定, 则函数调用的时候, 就无需再次指定

Match类简介

方法/属性 目的
group() 返回被正则表达式匹配到的字符串
start() 返回匹配的子字符串在字符串中的开始位置
end() 返回匹配的子字符串在字符串中的结束位置
span() 等价于(start(),end())
.string 待匹配字符串
.re 匹配时使用的正则表达式
.pos 正则表达式搜索文本的开始位置
.endpos 正则表达式搜索文本的结束位置

flags常用标记

常用标记(简写/全称) 说明
re.ASCII/re.A 使\w,\b,\s和\d只匹配ASCII字符
re.I/re.IGNORECASE 忽略正则表达式大小写
re.M/re.MULTILINE 使得正则表达式中的^从给定字符串的每一行开始匹配
re.S/re.DOTALL 使得.也可以匹配\n字符
re.VERBOSE/re.X 使正则表达式可以附加解释信息

正则表达式的两种调用方法

  1. 函数式用法
    • re.search(...)
  2. 面向对象用法
    • 编译后进行多次操作
      1
      2
      pat = re.compile(表达式)
      rst = pat.search(...)
    • 因为已经编译了, 所以各个函数, 相比于原来的函数, 就需要去掉表示正则表达式的参数

说明:得益于解释器缓存, 同一个正则表达式, 使用函数式调用时, 不会被多次编译, 因此两种方式的消耗基本相同

VERBOSE模式实例

1
2
3
4
5
6
7
8
9
charref = re.compile(r'''
&[#] # start of a numeric entity reference
(
0[0-7]+ #Octal form
| [0-9]+ #Decimal form
| x[0-9a-fA-F]+ #Hexadecimal form
)
; #Trailing semicolon
''',re.VERBOSE)

分组

  • 使用 “(“ 和 “)”可以对一个正则表达式分组, 从而提取出正则表达式匹配的字符串中的部分内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> m = re.match("(a(b)c)d","abcd")
    >>> m.group(0)
    'abcd'
    >>> m.group(1)
    'abc'
    >>> m.group(2)
    'b'
    >>> m.group(2,0,2)
    ('b', 'abcd', 'b')
    >>> m.groups()
    ('abc', 'b')
  • 在字符串中, 从左向右每遇到一个括号, 序号+1. 使用gruop(i)表示提取第i个括号中匹配的内容
  • 0表示匹配字符串本身, 所以无论正则表达式中是否含有分组, gruop(0)都是存在的
  • 接受多个参数时, 返回多个参数对于的内容的元组
  • 使用groups()返回所有分组匹配的字符串组成的元组(因此不包含直接匹配的正则表达式)

逆向引用(Backreferences)

  • 在一个正则表达式中, 可以使用\1,\2等由\加上一个数字来引用前面出现的分组,表示此处需要匹配前面分组匹配的字符串
    1
    2
    3
    >>> p = re.compile(r"(\b\w+)\s+\1")
    >>> p.search("Paris in the the spring").group()
    'the the'

括号中需要匹配一个单词, 此后\1表示此处匹配一个和括号相同的单词

Python的扩展正则表达式

注意:本节内容是正则表达式的扩展用法, 并非标准正则表达式语法

扩展正则表达式符号

符号 作用
(?...) 匹配…中的字符串, 但是不再捕捉其中的内容(语义标记)
(?P<name>...) 匹配…中的字符串, 同时此分组被命名为name

先行断言

先行断言(Lookahead Assertion)是一种特殊的判断方法, 有正向和负向两种方式, 具体如下表

断言表示 类型 含义
(?=…) 正向 匹配…中的正则表达式, 但是不消耗实际字符串的位置, 为真时, 才继续匹配
(?!…) 负向 匹配…中的正则表达式, 当与实际的字符串不匹配时为真, 否则整个正则表达式直接不匹配

扩展阅读

正则表达式扩展阅读
正则表达式扩展阅读

修改字符串

Python字符串相关函数

方法 目的
spilt() 分割字符串, 在匹配正则表达式的字符串处进行分割
sub() 查找匹配正则表达式的子字符串, 并替换为指定的字符串
subn() 和sub()函数效果相同, 增加额外的返回数据参数显示最大匹配次数
  • spilt(string[,maxsplit=0])
    • string:待匹配字符串
    • maxspilt:最大分割数
    • 如果正则表达式中含有捕捉分组, 则每次捕捉的结果会一同返回, 并且正好在两个分割字符串的结果之间
  • sub(replacement,string[,count=0])
    • replacement:替换成的字符串
    • string:待替换字符串
    • count:最大替换次数
    • 返回替换后的字符串, 如果没有匹配, 返回原来的字符串
  • subn()
    • 参数和sub()相同
    • 返回一个有两个元素的元组, 第一个元素是替换后的字符串, 第二个元素是替换次数
  • 其他说明
    • sub函数高级用法
      • 在sub函数的relpacement中, 也可以使用\1,\2等引用正则表达式中的捕捉
      • 如果使用了命名分组, 也可以使用\g<name>来应用, 注意<>是必须的
      • 使用\g<2>\2相同, 但可以减少歧义
      • relpacement也可以是一个接受match对象的函数, 从而实现更加复杂的功能

高级用法实例代码

1
2
3
4
5
6
7
def hexrepl(match):
'''Return the hex string for a decimal number'''
value = int(match.group())
return hex(value)

p = re.compile(r'\d+')
p.sub(hexrepl,'Call 65490 for printing, 49152 for user code.')

正则表达式最佳实践

  • 使用正则表达式还是字符串方法
    • 如果是无差别的文本替换, 应该使用字符串的replace()方法
    • 如果是无差别的单一字符替换, 应该使用字符串的translate()方法
    • 只有当替换操作是特异性的时候, 才应该考虑代价更高的正则表达式方法
  • match()还是search()
    • match()只从开始匹配, search()匹配任意位置
    • 只有在确实需要从开头开始匹配, 才应该使用match(), 不要执着于非要只使用match()
    • 正则表达式引擎会使用正则表达式的第一个确定字符进行匹配优化
  • 贪婪匹配还是最小匹配
    • 通常的重复匹配字符串都是贪婪匹配的, 即匹配尽可能长的字符串
    • 在各种重复匹配符后加上一个?表示最小匹配, 例如+?,*?,??或者{m,n}?
    • 最小匹配匹配尽可能短的字符串
  • 使用re.VERBOSE
    • 复杂的正则表达式的可读性较低, 使用测此标记可以增加表达式的可读性
    • 处理字符类中的空白字符, 正则表达式中的所有空白都会被忽略
    • 使用#添加注释

经典正则表达式实例

正则表达式 解释
^[A-Za-z]+$ 由26个字母组成的字符串
^[A-Za-z0-9]+$ 由26个字母和数字组成的字符串
^-?\d+$ 整数形式的字符串
^[0-9]*[1-9][0-9]*$ 正整数形式的字符串
[\u4e00-\u9fa5] 判断是不是中文字符

高级正则表达式实例

1
2
3
4
5
6
\s*                 #Skip leading whitespace
(?P<header>[^:]+) #Header name
\s* : #Whitespace, and a colon
(?P<value>.*) #The header's value -- *? used to
#lost the following trailing whitespace
\s*$ #Trailing whitespace to end-of-line

最后更新: 2024年04月18日 13:26

版权声明:本文为原创文章,转载请注明出处

原始链接: https://lizec.top/2017/08/08/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%AC%94%E8%AE%B0/