首页 > 编程语言 >第10章-Python正则表达式

第10章-Python正则表达式

时间:2024-10-24 10:52:50浏览次数:16  
标签:10 匹配 rs Python 正则表达式 字符串 print re

在日常生活中,经常会遇到访问一个新的网站时,只有注册成网站用户才能够登录使用。一个用户在填写注册信息时,通常会要求填写手机号、邮箱等信息,在填写信息的过程中网站会对填写的手机号、邮箱进行验证。那么这个验证的过程是如何实现的呢?其实原理很简单,就是使用正则表达式进行规则匹配,不符合规则的就是不合法格式。

本章将详细地介绍正则表达式的原理及使用方法。希望通过本章的学习,让你能够熟练掌握正则表达式的使用方法,并且能够将正则表达式应用到代码的编写中,用简单的方法解决复杂的问题。

10.1 re模块

在Python中内置了处理正则表达式的模块re模块,有了re模块使我们在程序中能够非常方便地使用正则表达式对字符串进行各种规则匹配检查。在re模块中封装了很多正则表达式相关的函数,其中比较常用的一个函数是match函数,它用于对字符串进行正则匹配。

match(pattern, string)函数描述如下:

  • 参数说明:pattern表示正则表达式;string表示待匹配字符串。
  • 功能:对待匹配字符串按照从左向右的顺序,使用正则表达式进行匹配。如果待匹配字符串从左侧开始有一个子串能够与正则表达式匹配成功,那么将不再向后继续匹配,此时match函数返回一个匹配成功的Match对象,否则返回None。

注意:match函数是从待匹配字符串的左侧开始匹配的,如果左侧没有匹配成功,那么将不会再向后进行匹配,整个匹配失败。

例10-1 匹配一个字符串中是否包含“python”子串

案例代码如下:

import re
str = "python java python c++"
rs = re.match("python",str)
print(rs)

解析:从运行结果可以看出,通过正则表达式成功匹配到了字符串“str”中的子串“python”​。Match对象中包含的match='python’指的是匹配成功的子串,span=(0, 6)指的是匹配成功的子串在原字符串中的索引范围,虽然在待匹配字符串“str”中包含两个“python”子串,但是只匹配成功了开头的“python”子串,没有再向后匹配。

如果想直接获取匹配成功的子串,可以调用Match对象的group方法实现。

例10-2 获取正则表达式匹配成功的子串

案例代码如下:

import re
str = "python java python c++"
rs = re.match("python",str)
print(rs.group())

10.2 单字符匹配

本小节主要讲解在正则表达式中如何对单字符进行匹配。在表中列出了一些常用的单字符匹配符号。
在这里插入图片描述
例10-3 使用点号“.”匹配除换行符“\n”之外的任意单个字符

案例代码如下:

import re
rs = re.match(".","1")  # 匹配一个包含数字的字符串
print(rs.group())
rs = re.match(".","a")  # 匹配一个包含单字符的字符串
print(rs.group())
rs = re.match(".","abc")    # 匹配一个包含多字符的字符串
print(rs.group())

从运行结果看,使用一个点号“.”成功匹配单个字符。注意示例代码中加粗的一行,使用点号匹配了一个包含多字符的字符串,虽然在这个字符串中有多个字符,但是通过点号只匹配到了其中的第一个字符“a”​。如果想匹配多个单字符,在正则表达式中就要写多个点号。

例10-4 使用多个点号匹配多个单字符

案例代码如下:

import re
# str = "python java python c++"
rs = re.match("...","abc")  # 使用三个点号匹配三个但字符串
print(rs.group())
rs = re.match(".","\n") # 不会匹配\n,返回None
print(rs)

解析:示例代码中匹配一个字符串中的多个单字符要用相同数量的点号进行匹配,如果字符很多,这种表达方式就很麻烦。在下一小节中,我们将介绍正则表达式中的数量表示方法,通过单字符匹配字符与数量表示结合使用将会很好地解决这个示例中的问题。

例10-5 使用“\s”匹配任意空白字符

案例代码如下:

import re
rs = re.match("\s","\t")  # 匹配(tab)键
print(rs)
rs = re.match("\s","\n")    # 匹配换行符
print(rs)
rs = re.match("\s"," ") # 匹配空格
print(rs)

解析:使用“\s”​(小写的字母s)成功匹配了几个常见的空白字符,相反“\S”​(大写的字母s)可以匹配非空白字符。

例10-6 匹配“​[ ]​”中列举的字符,​“​[ ]​”中列举的字符之间是或的关系

案例代码如下:

import re
# 匹配以小h或者大H开头的字符串
rs = re.match("[Hh]","hello")
print(rs)
rs = re.match("[Hh]","Hello")
print(rs)

# 匹配0到9任意一个数字,方法一
rs = re.match("[0123456789]","3")
print(rs)

# 匹配0到9任意一个数字,方法二
rs = re.match("[0-9]","3")
print(rs)

解析:由于在正则表达式“​[​]​”中列举的字符之间是或的关系,所以使用同一个正则表达式可以匹配以大H或者小h开头的字符。在使用正则表达式匹配0到9任意一个数字时,方法一的书写方式显得很麻烦,如果漏写了一个数字,将无法成功匹配相应数字。方法二通过在正则表达式中设置数字的开始和结束区间,可以非常容易地对0到9内的数字进行匹配,书写简洁高效。在对连续区间内的数字进行匹配时,推荐使用第二种方法。

10.3 数量表示

在10.2节中,我们已经学习了正则表达式的单字符匹配,但是在使用正则表达式的过程中,不可能只针对单个字符匹配,很多时候要对字符出现的次数进行匹配。本节将介绍正则表达式的数量表示方法,表列出了一些常用的数量表示符号。
在这里插入图片描述
例10-7 检查用户信息是否完整

案例代码如下:

import re

# 存储用户信息列表,每条月用户信息包含三个字段:姓名、手机号、年龄
user_infos = ["Tom,13812345678,20","David,,30","Lilei,18851888888,25"]
for user in user_infos:
    # 使用正则表达式检查用户信息是否完整
    rs = re.match("\w+,[0-9]{11},\d+",user)
    if rs != None:
        # 匹配成功打印用户信息
        print(f"用户信息:{rs.group()}")
    else:
        print("用户信息不完整!")

解析:使用正则表达式来验证每一个用户信息是否完整,正则表达式匹配失败的用户信息表示该用户信息缺失。

在例10-7中对手机号的验证有些“粗糙”​,只是简单地验证了用户的手机号是不是11位数字,并无法判断是不是一个合法的手机号码。下面我们通过一个更加严格的正则表达式匹配用户手机号,如果用户随便填写了11位数字作为手机号,那么这个手机号就是非法的。具体代码如例10-8所示。

例10-8 验证手机号是否合法

案例代码如下:

import re

def reg_phone(phone):
    # 合法的手机号规则:由11位数字组成
    # 第1位是1开头,第2位是3、5、7、8其中一个数字,第3位到第11位是0-9的数字
    rs = re.match("1[3578]\d{9}",phone)
    if rs == None:
        return False
    else:
        print(rs.group())   # 打印匹配成功的手机号
        return True

# 测试1:正确的手机号
print("----------测试1结果----------")
print(reg_phone("13612345678"))

# 测试2:错误的手机号
print("----------测试2结果----------")
print(reg_phone("14612345678")) # 第2位没有出现3、5、7、8其中一个

# 测试3:正确的手机号+字符
print("----------测试3结果----------")
print(reg_phone("13612345678abc"))

解析:测试1和测试2都返回了正确的结果,但是测试3返回的结果有些问题。接下来我们分析一下正则表达式没有识别出错误手机号的原因,正则表达式在对测试3的手机号进行验证时,目标字符串前半部分包含的手机号符合正则表达式的规则,当正则表达式检验完正确的手机号之后,没有对后半部分的“abc”再进行匹配,直接返回了匹配的手机号。在正则表达式中我们应该设置手机号的结束边界,如果在结束边界之外还有其他字符,则表示是非法的手机号。在下一小节10.4边界表示中,我们将结合正则表达式的边界表示规则,继续完善匹配手机号的正则表达式。

10.4 边界表示

在编写写正则表达式规则时,可以通过边界规则限制待匹配字符串的开始或者结束边界。常用的两个边界表示符号描述如表所示。
在这里插入图片描述
我们继续完善例10-8验证手机号是否合法的示例,在匹配长度超过11位的手机号时,需要限制手机号的结束边界,这样的正则表达式才能准确地验证一个手机号是否合法。

例10-9 结束边界在手机号验证中的应用

案例代码如下:

import re

def reg_phone(phone):
    # 合法的手机号规则:由11位数字组成
    # 第1位是1开头,第2位是3、5、7、8其中一个数字,第3位到第11位是0-9的数字
    rs = re.match("1[3578]\d{9}$",phone) # 正则表达式添加结束边界$
    if rs == None:
        return False
    else:
        print(rs.group())   # 打印匹配成功的手机号
        return True

# 测试1:正确的手机号
print("----------测试1结果----------")
print(reg_phone("13612345678"))

# 测试2:错误的手机号
print("----------测试2结果----------")
print(reg_phone("14612345678")) # 第2位没有出现3、5、7、8其中一个

# 测试3:正确的手机号+字符
print("----------测试3结果----------")
print(reg_phone("13612345678abc"))

在Python中,标识符的命名是不能够以数字开头的,下面我们通过一个示例来演示如何使用正则表达式来验证一个标识符是否合法。

例10-10 验证标识符命名是否合法

案例代码如下:

import re
def reg_identifier(str):
    # 正则表达式匹配以数字开头的字符串
    rs = re.match("^[0-9]\w*",str)
    if rs != None:
        # 如果str以数字开头,则表示是非法标识符
        return False
    else:
        return True

print(f"标识符1_name是否合法:{reg_identifier('1_name')}")
print(f"标识符name_1是否合法:{reg_identifier('name_1')}")

解析:在正则表达式中使用“^[0-9]​”匹配字符串开头的数字,使用“\w*”匹配标识符中除开头外剩余的0个或多个字符,凡是被这个正则表达式匹配成功的都是非法标识符。

10.5 转义字符

转义字符不是Python语言特有的,在很多编程语言中都把“\”作为转义字符,转义字符与一些普通字符组合,能够将这些普通的字符转换成具有特殊功能的字符,例如换行符“\n”​、制表符“\t”等。在正则表达式中使用转义字符的地方也非常多,如之前学习的单字符匹配符号,大部分都是使用转义字符与普通大小写字母组合而成的。有时也需要使用转义字符将一些具有特殊功能的字符转换成普通字符。

例10-11 使用正则表达式匹配163邮箱

import re
# 合法的163邮箱地址以4到10个单词字符开始,以@163.com结束
# 合法邮箱
rs = re.match("\w{4,10}@163.com$","python2024@163.com")
print(rs)
# 非法邮箱
rs = re.match("\w{4,10}@163.com$","abc@163.com")
print(rs)
# 非法邮箱
rs = re.match("\w{4,10}@163.com$","vip_python2024@163.com")
print(rs)

解析:从运行结果看,代码中的正则表达式能够准确地匹配出合法邮箱与非法邮箱,看似没有什么问题。接下来将一个合法的测试邮箱地址中的“.”用字母“h”代替,把它改成一个非法邮箱,使用这个正则表达式再次对这个邮箱进行匹配测试。

rs = re.match("\w{4,10}@163.com$","python2024@163.com")
print(rs)

解析:从运行结果看,这个正则表达式成功地匹配了修改之后的非法邮箱,原因是在正则表达式中“.”号具有特殊含义,它是一个单字符匹配符号,表示匹配除换行符“\n”之外的任意字符。解决办法是,在正则表达式中使用转义字符将“.”转换为普通字符。

# 非法邮箱
rs = re.match("\w{4,10}@163.com$","python2024@163hcom")
print(rs)
# 合法邮箱
rs = re.match("\w{4,10}@163.com$","python2024@163.com")
print(rs)

Python中的正则表达式执行流程如图10-1所示,在代码执行时,首先将正则表达式字符串传递给Python内置的正则表达式解析器,经过编译生成一个正则表达式对象,然后通过正则表达式对象去匹配待匹配字符串,最终返回匹配结果。

在这里插入图片描述
先看一个小例子,打印一个包含两个“\”的字符串。

str = "abc\\def"
print(str)

从运行结果看,只打印出了字符串中的一个“\”​,原因是什么呢?是因为“\”具有转义功能,两个斜杠中的第一个斜杠是将第二个斜杠转换成了普通的斜杠,听着有些绕,说白了就是用两个斜杠表示一个斜杠。同理,打印两个斜杠,在字符串定义时要写4个斜杠,如果打印多个斜杠,在字符串定义时要写的斜杠数量将会非常多,而且很有可能出现漏写或多写斜杠的情况。Python中的原生字符串可以很好地解决这个问题,原生字符串的表示方法就是在字符串前添加一个字母“r”​。下面使用原生字符串改造刚才的代码。

str = r"abc\\def"
print(str)

从运行结果看,正常地打印出了字符串中的两个斜杠。原生字符串可以取消字符串中斜杠的特殊功能,使它只表示普通的字符。掌握了原生字符串的用法之后,下面把原生字符串应用到正则表达式中。

例10-12 匹配字符串中的斜杠“\”

案例代码如下:

import re
str = "\python"
# 使用非原生字符串定义正则表达式匹配规则
rs = re.match("\\\\\w+",str)
print(str)
# 使用原生字符串定义正则表达式匹配规则
rs = re.match(r"\\\w+",str)
print(str)

解析:从运行结果看,使用非原生字符串定义的正则表达式规则中用了4个斜杠去匹配1个斜杠,使用原生字符串定义的正则表达式规则用了两个斜杠去匹配1个斜杠。下面我们再结合分析下正则表达式的执行流程。

首先,分析使用非原生字符串定义的正则表达式的执行流程。因为字符串中斜杠具有转换功能,所以正则表达式字符串"\\\\\w+"中的前4个斜杠会被转换成两个斜杠,变成“\\\w+”传递给正则表达式解析器。在正则表达式中斜杠也具有转换功能,所以解析器会将“\\\w+”中的前两个斜杠转换成1个斜杠,变成“\\w+”去匹配字符串"\python"(注意:​“\w+”中的第1个斜杠是普通斜杠,第2个斜杠是与字母“w”组成的匹配单词字符的符号)​。

然后,分析使用原生字符串定义的正则表达式的执行流程。因为使用原生字符串定义的正则表达式字符串已经取消了斜杠的转义功能,所以会以“\\\w+”的形式传递给正则表达式解析器,解析器将“\\\w+”中的前两个斜杠转换成1个斜杠,变成“\\w+”去匹配字符串"\python"

通过前面的分析,使用原生字符串定义的正则表达式更加简洁,代码可读性更强,推荐使用原生字符串编写正则表达式。

10.6 匹配分组

在10.5一节的例10-11中定义的正则表达式只能匹配一种163的邮箱,如果想匹配多种类型的邮箱,可以使用分组来实现。
在这里插入图片描述
例10-13 使用分组匹配多种类型邮箱

import re
# 匹配163、QQ、Outlook的邮箱
rs1 = re.match(r"\w{4,10}@(163|qq|outlook)\.com$","python2024@163.com")
print(rs1)
rs2 = re.match(r"\w{4,10}@(163|qq|outlook)\.com$","python2024@qq.com")
print(rs2)

解析:在代码的正则表达式中,把需要匹配的邮箱类型用一对括号括起来表示一个分组,在分组内使用竖线“|”分隔不同的邮箱类型,邮箱类型之间是或的关系,表示匹配成功列举出来的任何一个邮箱类型都是匹配成功的。

在HTML中所有的信息都是用标签定义的,并且标签是成对出现的,例如,定义网页标题的标签“<title></title>”​,结束标签会比开始标签多一个反斜杠。当需要使用程序处理HTML数据时,就需要使用正则表达式来匹配需要的标签,获取指定标签内的数据,例如,使用爬虫爬取网页数据。

例10-14 匹配网页标签

案例代码如下:

import re
html_data= "<head><titile>python</titile></head>"
rs = re.match(r"<.+><.+>.+</.+></.+>","html_data")
print(rs)

解析:正则表达式中的每一个“<.+>”都用于匹配一个开始标签,每一个“</.+>”都用于匹配一个结束标签。从运行结果看,这个正则表达式成功匹配了网页中的标签。

接下来我们交换两个结束标签的位置,交换位置之后成对的开始标签和结束标签就会错位,再次验证例10-14中的正则表达式是否匹配这个错误的网页标签。

import re
html_data_err = "<head><titile>python</titile></head>"
rs = re.match(r"<.+><.+>.+</.+></.+>","html_data_err")
print(rs)

解析:从运行结果看,正则表达式成功匹配了这个错误的网页标签,说明这个正则表达式编写得不够健壮,不能够识别出错误的网页标签。那我们尝试使用分组来解决这个问题,看看能不能将错误的网别标签识别出来。

例10-15 使用正则表达式分组匹配网页标签

案例代码如下:
在这里插入图片描述
解析:从运行结果看,使用正则表达式分组识别出了错误网页标签,没有匹配成功。正确的网页标签被正则表达式成功匹配出来。正则表达式中的每一个分组“(.+)”匹配一个标签名称,默认每个分组有一个编号,编号从1开始。正则表达式中的“\1”和“\2”表示引用前面对应编号的分组规则匹配的标签名称,这样就能够正确地匹配成对的开始标签和结束标签。

当正则表达式中的分组比较多,再使用“\NUM”引用分组就会比较麻烦,需要记住每一个分组的编号,一不小心引用了错误的分组编号,那么这个正则表达式就是错误的。这个问题我们可以通过给分组起别名来解决,通过别名引用已经定义过的分组。

例10-16 通过正则表达式给分组起别名

案例代码如下:
在这里插入图片描述
解析:在正则表达式中分组小括号内的“?P”是固定语法,表示给分组起别名,​“g1”就是一个组名称(名称可以自定义)​,当正则表达式的后面需要引用前面已经定义过的分组时,只需要通过“?P=组名称”的方式引用即可。在正则表达式中使用分组别名优点是引用分组明确,不容易产生错误;缺点是定义别名和引用别名都要额外添加其他语法,使表达式结构更加复杂。建议当正则表达式中分组比较少时,可以通过编号引用分组,当分组比较多时,可以考虑使用别名引用分组。

10.7 内置函数

为了方便开发者编写正则表达式,Python为开发者提供了很多操作正则表达式的内置函数。开发过程中,合理地使用Python内置函数,可以简化开发过程,提高开发效率。本节将详细讲解几个比较常用的正则表达式内置函数。

  1. compile函数

在10.5一节的例10-12中,如果使用一个正则表达式对多个邮箱地址进行匹配,就需要把一个正则表达式重复写多次。Python为我们提供了另外一种简洁的方法,使用re模块内置的compile函数编译正则表达式,返回一个正则表达式对象,在匹配时可以多次复用一个正则表达式对象进行匹配。

例10-17 复用正则表达式对象匹配邮箱地址

案例代码如下:

import re
pattern = re.compile("\w{4,10}@163\.com$")  # 返回正则表达式对象
rs = re.match(pattern,"python2024@163.com")
print(rs)
rs = re.match(pattern,"vip_python2024@163.com")
print(rs)
rs = re.match(pattern,"abc@163.com")
print(rs)
  1. search函数

re模块内置的search函数的功能是从左到右在字符串的任意位置搜索第一个被正则表达式匹配的子字符串。

例10-18 从字符串中查找是否包含“python”​

案例代码如下:

import re
rs = re.search("python","hi python,i am going to study python")
print(rs)
  1. findall函数

re模块内置的findall函数的功能是在字符串中查找正则表达式匹配成功的所有子字符串,返回匹配成功的结果列表。

例10-19 从用户信息中查找出所有手机号

案例代码如下:

import re
infos = "Tom:13800000001,David:13800000002"
list = re.findall(r"1[3578]\d{9}",infos)
print(list)

解析:findall函数将所有匹配成功的手机号放入列表中,如果想查看这些手机号,只需要遍历列表即可。

例10-20 从用户信息中查找出所有邮箱地址

案例代码如下:

import re
infos = "Tom:python_vip@163.com,David:12345@qq.com"
list = re.findall(r"(\w{3,20}@(163|qq)\.com)",infos)
print(list)

解析:当正则表达式中含有两个及以上分组,会把一个表达式内不同分组匹配的结果组成一个元组放入列表。从运行结果看,用于匹配邮箱的正则表达式含有两个分组,外层括号括起来的分组用于匹配整个邮箱,内层括号括起来的分组用于匹配邮箱类型,所以结果列表中,每一个元素(元组类型)是正则表达式匹配的一个结果,元组内的每个元素都是每个分组匹配的结果。

  1. finditer函数

re模块内置的finditor函数的功能是在字符串中查找正则表达式匹配成功的所有子字符串,返回结果是一个可迭代对象Iterator,Iterator中的每个元素都是正则表达式匹配的一个子字符串。

例10-21 从用户信息中查找出所有手机号

案例代码如下:

import re
infos = "Tom:13800000001,David:13800000002"
iter_obj = re.finditer(r"1[3578]\d{9}",infos)
for iter in iter_obj:
    print(iter.group())

解析:从运行结果看,finditor函数将正则表达式匹配的手机号封装到可迭代对象“iter_obj”中,通过循环遍历“iter_obj”对象中的所有手机号。

  1. sub函数

re模块内置的sub函数的功能是将正则表达式匹配到的子字符串使用新的字符串替换掉。返回结果是替换之后的新字符串,原字符串值不变。

例10-22 将字符串中的所有空格替换成逗号

案例代码如下:

import re
stu = "Tom 13800000001 Male"
stu_new = re.sub("\s",",",stu)
print(f"stu={stu}")
print(f"stu_new={stu_new}")

解析:从运行结果看,sub函数将原字符串“stu”中的空格替换成了逗号,返回一个新的字符串,原字符串值不变。

10.8 贪婪与非贪婪模式

  • 贪婪模式:Python中的正则表达式解析器默认采用贪婪模式去匹配字符,也就是尽可能多地匹配字符。
  • 非贪婪模式:与贪婪模式相反,是尽可能少地匹配字符。

启用非贪婪模式的方法:在表示数量的符号,如“*”​“?”​“+”​“{m,n}”等的后边添加一个问号“?”​,这样正则表达式解析器就会采用非贪婪模式去匹配字符。

例10-23 贪婪与非贪婪模式对比

案例代码如下:

import re

# 贪婪模式
print("----------贪婪模式----------")
rs = re.findall("python\d*","python2024")   # 任意多个数字
print(rs)
rs = re.findall("python\d+","python2024")   # 至少出现一次数字
print(rs)
rs = re.findall("python\d{2,}","python2024")    # 至少出现两次数字
print(rs)
rs = re.findall("python\d{1,4}","python2024")   # 出现1到4次数字
print(rs)

# 非贪婪模式
print("----------非贪婪模式----------")
rs = re.findall("python\d*?","python2024")
print(rs)
rs = re.findall("python\d+?","python2024")
print(rs)
rs = re.findall("python\d{2,}?","python2024")
print(rs)
rs = re.findall("python\d{1,4}?","python2024")
print(rs)

解析:从运行结果看,在贪婪模式下,每个正则表达式都会尽可能多地匹配字符,也就是能多匹配一个字符就匹配上。在非贪婪模式下,每个正则表达式都会尽可能少地匹配字符,也就是能不匹配字符的就不匹配。

标签:10,匹配,rs,Python,正则表达式,字符串,print,re
From: https://blog.csdn.net/weixin_45086637/article/details/143158961

相关文章

  • Windows、macOS和Linux系统安装Python的指南
    在不同的操作系统上安装Python通常涉及几个简单的步骤。以下是在Windows、macOS和Linux系统上安装Python的指南:Windows系统安装Python:下载Python安装程序:访问Python官方网站 python.org。选择适合Windows的Python版本(确保下载最新稳定版)。运行安装程序:双击下载的.exe文......
  • 1024程序员节:为何不放假?—— 一场关于社会地位与资源分配的反思
    目录一场关于社会地位与资源分配的反思IT从业者的社会贡献对比其他行业医生教师公务员程序员的现状社会地位工作环境呼吁改变企业责任社会认知个人觉醒结语一场关于社会地位与资源分配的反思在这个数字化时代,信息技术从业者(IT从业者)无疑是推动社会进步的重要力......