第16章 正则表达式

正则表达式(Regular Expression,在代码中常简写为regx、regxp、RE或re)是预先定义好的一个“规则字符串”,通过这个“规则字符串”可以匹配、查找和替换那些符合“规则”的文本。

正则表达式比使用字符串简单。

16.1 正则表达式中的字符

 正则表达式是一种字符串,由普通字符和元字符(Metacharacters)组成的。

1)普通字符

按照字面意思表示的字符。标号为②的字符。

验证邮箱zhijieketang.com的正则表达式

 2)元字符

预先定义好的一些特定字符。

16.1.1 元字符

 元字符(Metacharacters)是用来描述其他字符的特殊字符,由基本元字符和普通字符构成。基本元字符是构成元字符的组成元素,主要有14个。

 16.1.2 字符转义

\w+@zhijieketang\.com

• 在正则表达式中有时也需要字符转义,w字符不是表示英文字母w之意使用,而是想表示任何语言的单词字符(如:英文字母、亚洲文字等)、数字和下划线等内容,那么需要在w字母之前加上反斜杠(\),反斜杠 (\)也是基本元字符,与Python语言中的字符转义是类似的。

• 不仅可以对普通字符进行转义,还可以对基本元字符进行转义,点(.)字 符是希望按照点(.)字面意义使用,作为.com域名的一部分,而不是作为 “.”基本元字符使用,所以需要加反斜杠(\)进行转义,即“\.”才是表示点 (.)字面意义。\.com表示匹配.com域名。

16.1.3 开始与结束字符

 基本元字符^和$,它们可以用于匹配一行字符串的开始和结束。当正则表达式以“^”开始时,则从字符串的开始位置匹配;当正则表达式以“$”结束时, 则从字符串的结束位置匹配。所以正则表达式“\w+@zhijieketang\.com”和 “^\w+@zhijieketang\.com$”是不同的。

import re   # 导入表达式模块re

p1 = r'\w+@zhijieketang\.com'   # 或 '\\w+@zhijieketang\\.com'
p2 = r'^\w+@zhijieketang\.com$'   # 或 '^\\w+@zhijieketang\\.com$'
# 定义了两个正则表达式p1和p2

text = "Tony's email is tony_guan588@zhijieketang.com."
m = re.search(p1, text)
print(m)    # 匹配

m = re.search(p2, text)
print(m)    # 不匹配

email = 'tony_guan588@zhijieketang.com'
m = re.search(p2, email)
print(m)    # 匹配

 search()函数在字符串text查找p1正则表达式,如果找到返回match,如果没找到返回return。

提示:如果正则表达式采用r表示原始字符串(rawstring)其中字符环节不需要转义。 

16.2 字符类

正则表达式可以使用字符类(Character class),一个字符类定义一组字符集合,其中的任一字符出现在输入字符串中即匹配成功。注意每次只能匹配字符类中的一个字符。

16.2.1 定义字符类

定义一个普通的字符类需要使用“[”和“]”元字符类。例如想在输入字符串中匹 配Java或java,可以使用正则表达式[Jj]ava。

import re

p = r'[Jj]ava'

m = re.search(p, 'I like Java and Python')
print(m)    # 匹配

m = re.search(p, 'I like JAVA and Python')
print(m)    # 不匹配

m = re.search(p, 'I like java and Python')
print(m)    # 匹配

p = r'JAVA|Java|java'
m = re.search(p, 'I like JAVA and Python')
print(m)    # 匹配

提示:JAVA也能匹配,使用正则表达式JAVA|Java|java,其中“|”是基本元字符,表示“或关系”。

16.2.2 字符类取反

正则表达式中指定不想出现的字符,在字符类前面加“^”符号。

import re

p = r'[^0123456789]'    # 定义正则表达式[^0123456789],表示输入字符串出现非0~9数字匹配

m = re.search(p, '1000')
print(m)    # 不匹配

m = re.search(p, 'Python 3')
print(m)    # 匹配

 16.2.3 区间

 正则表达式[^0123456789]有些麻烦,这种连续数字可以使用区间来表示。

区间是用连字符“-”表示的,例如[0123456789]采用区间表示为[0-9],[^0123456789]采用区间表示为[^0-9],[a-z]表示所有小写字母类,[A-Z]表示所有大写字母类。

也可以表示多个不同的区间。[A-Za-z0-9]表示所有字母和数字字符类,[0-25-7]表示0、1、2、5、6、7几个字符组成的字符类。

import re

m = re.search(r'[A-Za-z0-9]', 'A10.3')
print(m)    # 匹配

m = re.search(r'[0-25-7]', 'A3489C')
print(m)    # 不匹配

16.2.4 预定义字符类

 有些字符类很常用,例如[0-9],为了书写方便,正则表达式提供了预定义的字符类。例如预定义字符类\d等价于[0-9]字符类。

import re

# p = r'[^0123456789]'
p = r'\D'

m = re.search(p, '1000')
print(m)    # 不匹配

m = re.search(p, 'Python 3')
print(m)    # 匹配

text = '你们好hello'
m = re.search(r'\w', text)
print(m)    # 匹配

16.3 量词 

匹配显示多次字符或字符串可以使用量词。

16.3.1 使用量词

量词表示字符或字符串重复的次数。

import re

m = re.search(r'\d?', '87654321')   # 出现数字一次
print(m)    # 匹配字符'8'

m = re.search(r'\d?', 'ABC')   # 出现数字零次
print(m)    # 匹配字符''

m = re.search(r'\d*', '87654321')   # 出现数字多次
print(m)    # 匹配字符'87654321'

m = re.search(r'\d*', 'ABC')   # 出现数字零次
print(m)    # 匹配字符''

m = re.search(r'\d+', '87654321')   # 出现数字多次
print(m)    # 匹配字符'87654321'

m = re.search(r'\d+', 'ABC')
print(m)    # 不匹配

m = re.search(r'\d{8}', '87654321')   # 出现数字8次
print(m)    # 匹配字符'87654321'

m = re.search(r'\d{8}', 'ABC')
print(m)    # 不匹配

m = re.search(r'\d{7,8}', '87654321')   # 出现数字8次
print(m)    # 匹配字符'87654321'

m = re.search(r'\d{9,}', '87654321')
print(m)    # 不匹配

 16.3.2 贪婪量词和懒惰量词

量词细分为贪婪量词和懒惰量词,贪婪量词会尽量匹配字符,懒惰量词会尽可能少地匹配字符。Python中默认的正则表达式量词默认是贪婪的,要想使用懒惰量词在量词后面加上“?”即可。

import re

# 使用贪婪量词
m = re.search(r'\d{5,8}', '87654321')   # 出现数字8次
print(m)    # 匹配字符'87654321'

# 使用懒惰量词
m = re.search(r'\d{5,8}?', '87654321')   # 出现数字8次
print(m)    # 匹配字符'87654'

16.4 分组

 如果想让一个字符串作为整体使用量词,则需要对整体字符串进行分组,也称子表达式

16.4.1 定义分组

定义正则表达式分组,则需要将字符串放到一对小括号中。

import re

p = r'(121){2}' # 将121字符串分为一组,(121){2}表示对121字符串重复两次,即121121
m = re.search(p, '121121abcabc')
print(m)    # 匹配
print(m.group())    # 返回匹配字符串
print(m.group(1))   # 获得第一组字符串内容

p = r'(\d{3,4})-(\d{7,8})'  # 用来验证固定电话号码,-前是3、4位的区号,-之后的是7、8位的电话号码
m = re.search(p, '010-87654321')
print(m)    # 匹配
print(m.group())    # 返回匹配字符串
print(m.groups())   # 获得所有组内容

调用match对象的group()方法返回匹配的字符串,group()方法语法:

match.group([group1, ...]) 

其中参数group1是组编号,在正则表达式式中编号是从1开始的

match对象.groups()方法返回所有分组,返回值是一个元组。   

16.4.2 命名分组

Python程序访问元组,除了可以通过组编号进行访问,还可以通过组名进行访问,前提是要在正则表达式中为组命名。组命名通过在组开头添加“?P<组名>”实 现。

import re

p = r'(?P<area_code>\d{3,4})-(?P<phone_code>\d{7,8})'
m = re.search(p, '010-87654321')
print(m)    # 匹配
print(m.group())    # 返回匹配字符串
print(m.groups())   # 获得所有组内容

# 通过组编号返回组内容
print(m.group(1))
print(m.group(2))

# 通过组名返回组内容
print(m.group('area_code'))
print(m.group('phone_code'))

16.4.3 反向引用分组

• 除了可以在程序代码中访问正则表达式匹配之后的分组内容,还可以在正则表达式内部访问之前的分组,称为“反向引用分组” 。

• 正则表达式中反向引用语法是“\组编号”,组编号是从1开始的。如果组有名字,也可以组名反向引用,语法是“(?P=组名)”

假设由于工作需要想解析一段XML代码,需要找到某一个开始标签和结束标签。

import re

p = r'<([\w]+)>.*</([\w]+)>' # 定义的正则表达式被分成相同的两组
# 不能保证开始标签和结束标签是一致的

m = re.search(p, '<a>abc</a>')
print(m)    # 匹配

m = re.search(p, '<a>abc</b>')  # <a>abc</b>不是有效的XML代码,因为开始标签和结束标签是一样的
print(m)    # 匹配

import re

# p = r'<([\w]+)>.*</\1>'   # 使用组编号反向引用,\1是反向引用第一个组
p = r"<(?P<tag>[\w]+)>.*</(?P=tag)>"    # 使用组名反向引用
# "?P<tag>命名组名为tag",(?P=tag)是通过组名反向引用

m = re.search(p, '<a>abc</a>')
print(m)    # 匹配

m = re.search(p, '<a>abc</b>')
print(m)    # 不匹配

  16.4.4 非捕获分组 

 前面介绍的分组称为“捕获分组”,捕获分组的匹配结果被暂时保存到内存中,以备正则表达式或其他程序引用,这个过程称为“捕获”。捕获结果可以通过组编号或组名进行引用。能够反向引用分组就是因为分组是捕获的。

捕获分组的匹配结果被暂时保存在内存中,如果正则表达式比较复杂,可能影响性能。所以使用分组,但又不需要引用分组时,使用“非捕获分组”,在组的开头使用“?:”可以实现非捕获分组

修改16.4.1的代码,使用非捕获分组。

import re

p = r'(?:121){2}'
m = re.search(p, '121121abcabc')
print(m)    # 匹配
print(m.group())    # 返回匹配字符串
# print(m.group(1))   # 试图获得第一组字符串内容发生错误

p = r'(?:\d{3,4})-(?:\d{7,8})'
m = re.search(p, '010-87654321')
print(m)    # 匹配
print(m.group())    # 返回匹配字符串
print(m.groups())   # 获得所有组内容

注意:使用非捕获分组不会匹配组内容。m.groups()方法获得所有组内容结果也是空。

16.5 re模块中重要函数

16.5.1 search()和match()函数

๏ search():在输入字符串查找,返回第一个匹配内容,如果找到则返回match对象,如果没有找到返回None。

๏ match():在输入字符串开始处查找匹配内容,如果找到则match对象,如果没有找到返回None。

import re

p = r'\w+@zhijieketang\.com'

text = "Tony's email is tony_guan588@zhijieketang.com."
m = re.search(p, text)
print(m)    #匹配

m = re.match(p, text)
print(m)    #不匹配

email = 'tony_guan588@zhijieketang.com'
m = re.search(p, email)
print(m)    #匹配

m = re.match(p, email)
print(m)    #匹配

# match对象几个方法
print('match对象几个方法')
print(m.group())
print(m.start())
print(m.end())
print(m.span())

 

group()方法返回匹配的子字符串。

start()方法返回子字符串的开始索引

end()方法返回子字符串的结束索引

span()方法返回子字符串的跨度,它是一个二元素的元组

16.5.2 findall()和finditer()函数

๏ findall():在输入字符串中查找所有匹配内容,如果匹配成功,则返回match列表对象,如果匹配失败则返回None。

๏ finditer():在输入字符串中查找所有匹配内容,如果匹配成功,则返回容纳match的可迭代对象,通过迭代对象每次可以返回一个match对象,如果匹配失败则返回None。

import re

p = r'[Jj]ava'
text = 'I like Java and java.'

match_list = re.findall(p, text)
print(match_list)

match_iter = re.finditer(p, text)   # finditer()函数返回可迭代对象
for m in match_iter:    # for循环遍历可迭代对象
    print(m.group())

16.5.3 字符串分割 

字符串分割使用split()函数,按照匹配的子字符串进行字符串分割,返回字符串列表对象。

split()函数语法结构:

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

参数pattern是正则表达式

参数string是要分割的字符串

参数maxsplit是最大分割次数,maxsplit默认值为零,表示分割次数没有限制

参数flags是编译标志

import re

p = r'\d+'  # 正则表达式\d+匹配一个到多个数字
text = 'AB12CD34EF'

clist = re.split(p, text)
print(clist)

clist = re.split(p, text, maxsplit=1)
print(clist)

clist = re.split(p, text, maxsplit=2)
print(clist)

16.5.4 字符串替换

字符串替换使用sub()函数,用于替换搭配的子字符串,返回值是替换之后的字符串,sub()函数语法如下:

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

参数pattern是正则表达式

参数repl是要替换字符串

参数string是要提供的字符串

参数count是要替换的最大数量,默认值为零,表示替换数量没有限制

参数flags是编译标志

import re

p = r'\d+'  # 正则表达式\d+匹配一个到多个数字
text = 'AB12CD34EF'

repace_text = re.sub(p, ' ', text)
print(repace_text)

repace_text = re.sub(p, ' ', text, count=1)
print(repace_text)

repace_text = re.sub(p, ' ', text, count=2)
print(repace_text)

 16.6 编译正则表达式

为了提高效率,还可以对Python正则表达式进行编译,编译的正则表达式可以重复使用,这样能减少正则表达式的解析和验证。

在re模块中compile()函数可以编译正则表达式,compile()函数语法如下:

re.compile(pattern[, flags=0])

参数pattern是正则表达式,参数flags是编译标志。

complie()函数返回一个编译的正则表达式对象regex。

 16.6.1 已编译正则表达式对象

complie()函数返回一个编译的正则表达式对象regex,提供了文本的匹配、查找和替换等操作,与re模块函数功能类似。

 参数pos为开始查找的索引,参数endpos为结束查找的索引。

import re

p = r'\d+'  # 正则表达式\d+匹配一个到多个数字
regex = re.compile(p)   # 编译正则表达式

text = "Tony's email is tony_guan588@zhijiekeang.com."
m = regex.search(text)
print(m)    # 匹配

m = regex.match(text)
print(m)    # 不匹配

p = r'[Jj]ava'
regex = re.compile(p)   # 编译正则表达式
text = 'I like Java and java.'

match_list = regex.findall(text)
print(match_list)   # 匹配

match_iter = regex.finditer(text)
for m in match_iter:
    print(m.group())

p = r'\d+'
regex = re.compile(p)   # 编译正则表达式
text = 'AB12CD4EF'

clist = regex.split(text)
print(clist)

repace_text = regex.sub(' ', text)
print(repace_text)

 16.6.2 编译标志

 compile()函数编译正则表达式对象时,还可以设置编译标志,编译标志可以改变正则表达式引擎行为。

๏ 1. ASCII和Unicode

预定义类\w匹配单词字符,在Python2中是ASCII编码,在Python3中是Unicode编码,所以包含任何语言的单词字符。

通过编译标志re.ASCII(或re.A)设置采用ASCII编码,通过编译标志re.UNICODE(或re.U)设置采用Unicode

import re

text = '你们好Hello'

p = r'\w+'
regex = re.compile(p, re.U)

m = regex.search(text)
print(m)    # 匹配

m = regex.match(text)
print(m)    # 匹配

regex = re.compile(p, re.A)

m = regex.search(text)
print(m)    # 匹配

m = regex.search(text)
print(m)    # 不匹配

๏ 2. 忽略大小写

默认情况正则表达式引擎对大小写是敏感的,但有时在匹配中需要忽略大小写,可以通过编译标志re.IGNORECASE(或re.I)实现。

import re

p = r'(java).*(python)'
regex = re.compile(p, re.I)

m = regex.search('I like Java and Python.')
print(m)    # 匹配

m = regex.search('I like JAVA and Python.')
print(m)    # 匹配

m = regex.search('I like java and Python.')
print(m)    # 匹配

๏ 3. 点(.)元字符匹配换行符

默认情况正则表达式引擎中点“.”元字符可以匹配除换行符外的任何字符,但是有时需要“.”元字符也能匹配换行符,可以通过编译标志re.DOTAALL(或re.S)实现。

import re

p = r'.+'
regex = re.compile(p)

m = regex.search('Hello\nWorld.')
print(m)    # 匹配

regex = re.compile(p, re.DOTALL)

m = regex.search('Hello\nWorld.')
print(m)    # 匹配

๏ 4. 多行模式

编译标志re.MULTLINE(或re.M)可以设置为多行模式,多行模式对于元字符^和$行为会产生影响。默认情况,^和$匹配字符串的开始和结束,在多行模式下^和$匹配任意一行的开始和结束。

import re

p = r'^World'
regex = re.compile(p)

m = regex.search('Hello\nWorld.')
print(m)    # 不匹配

regex = re.compile(p, re.M)

m = regex.search('Hello\nWorld.')
print(m)    # 匹配

๏ 5. 详细模式

编译标志re.VERBOSE(或re.X)可以设置详细模式,详细模式下可以在正则表达式中添加注释,可以有空格和换行,便于阅读。

import re

p = """(java)       # 匹配java字符串
        .*          # 匹配任意字符零或多个
        (python)    # 匹配python字符串
    """
regex = re.compile(p, re.I | re.VERBOSE)

m = regex.search('I like Java and Python')
print(m)    # 匹配

m = regex.search('I like JAVA and Python')
print(m)    # 匹配

m = regex.search('I like java and Python')
print(m)    # 匹配

定义的正则表达式原本是(java).*(python)。由于正则表达式包含了换行等符号,所以需要使用双重单引号或三重双引号括起来,而不是使用原始字符串。

当需要设置多编译标志时,编译标志之间需要位或运算符“|”。

  • 45
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值