python正则表达式入门

简介: python正则表达式入门

引言

如果我们希望在字符串里面去寻找电话号码,电话号码的模式一般是这样的:3个数字+一个短横线+3个数字+一个短横线+4个数字。比如415-555-4242

如果我们希望通过一个函数来实现检查字符串是否匹配模式,它会返回True或者False,代码如下:

def isPhoneNumber(text):
    if len(text)!=12:
        return False
    #isdigit()函数用来判断一个字符串是否全是数字
    for i in range(0,12):
        if i==3 or i==7:
           if text[i] != '-':
            return False
        else:
            if not text[i].isdigit():
                return False
    return True
for i in range(3):
    s=input("请输入要匹配的电话号码:")
    if isPhoneNumber(s):
        print("{}是合法的".format(s))
    else:
        print("{}是不合法的".format(s))

运行是这样的:

请输入要匹配的电话号码:123-456-7890
123-456-7890是合法的
请输入要匹配的电话号码:12-354678-199
12-354678-199是不合法的
请输入要匹配的电话号码:1234665758889-0
1234665758889-0是不合法的

正则表达式查找文本模版

前言

前面的电话号码查找程序能工作,但是它只能实现对一种电话号码模式的匹配,象123.456.7890/(123)456-7890也是合理的电话号码模式,当我们对它们进行匹配的时候就要去增加逻辑,但是有没有更简单的方式呢?这就是我们今天要提到的正则表达式

常见的匹配字符

  • #### . 通配符,匹配任意1个字符(除了\n换行)
    [ ] 匹配[ ]中列举的字符,里面的元素之间存在“或”的关系,[abc]只能匹配a或b或c
    \d 匹配数字,等价于[0,9]
    \D 匹配非数字,等价于[^0-9]
    \s 匹配空白,常见的空白符的形式有空格、制表符 (\t)、换行 (\n) 和回车 (\r)
    \S 匹配非空白
    \w 匹配非特殊字符,即a-z、A-Z、0-9、_、中文
    \W 匹配特殊字符

创建正则表达式对象

python里面有关于正则表达式的函数都在re模块,它在我们安装的python环境中,所以我们只需要导入即可:

import re

我们可以向re.complie()中传入一个字符串,表示正则表达式,它将返回一个Regex模式对象,例如如果要创建一个Regex对象来匹配电话号码,只需要:

phoneNumber=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')

匹配Regex对象

Regex对象的searc()方法查找输入的字符串,以寻找该正则表达式的所有匹配。如果未找到,则返回None,如果找到了该模式,search将返回一个Match对象,而它里面又一个**group()**方法返回所有实际上匹配的文本:

import re
phoneNumber=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
mo=phoneNumber.search("My name is 147-907-7916")
print("The phone number:{}".format(mo.group()))

输出为:

The phone number:147-907-7916

利用正则表达式实现匹配更多模式

上面我们介绍了python创建和查找正则表达式对象的基本步骤,现在让我们来尝试一些更加强大的匹配模式

利用括号分组

假如我们想把电话号码的区号从代码里面分离,可以添加括号在代码里面添加分组然后从分组里面获得匹配文本。

正则表达式的第几组括号就是第几组,向group输入n就可以获得第n组获得的文本信息,而传0或不传就是获得全部的匹配文本,示例如下:

phonenumber2=re.compile(r'(\d\d\d)-(\d\d\d\d-\d\d\d\d)')
mo=phonenumber2.search("My phoneNumber is 147-9037-7916")
areaCode,mainNumber=mo.groups()
print("区号:{},电话号码:{},完整号码:{}".format(areaCode,mainNumber,mo.group()))

输出为:

区号:147,电话号码:9037-7916,完整号码:147-9037-7916

注意:括号在正则表达式里面有特殊的含义,如果我们在匹配的时候想匹配括号,我们可以使用\符号对括号进行字符转义,示例如下:

phonenumber2=re.compile(r'(\d\d\d)-(\(\d\d\d\d-\d\d\d\d\))')
mo=phonenumber2.search("My phoneNumber is 147-(9037-7916)")
areaCode,mainNumber=mo.groups()
print("区号:{},电话号码:{},完整号码:{}".format(areaCode,mainNumber,mo.group()))

输出为:

区号:147,电话号码:(9037-7916),完整号码:147-(9037-7916)

而在后面我们会遇到其他有特殊含义的字符,如果我们在匹配的时候想匹配它们,我们可以也使用\符号对它们进行字符转义。

用管道匹配多个分组

我们将字符**|**称为“管道”,当我们希望匹配许多表达式中的一个就可以使用它,如下:

heroRegex1=re.compile(r'\d\d\d-\d\d-\d\d|\d\d\d\d-\d\d')
mo1=heroRegex1.search("456-78-90 9876-54 3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34")
print(mo1.group())

输出结果为:

456-78-90

我们尝试交换匹配文本的数据:

heroRegex2=re.compile(r'\d\d\d\d-\d\d|\d\d\d-\d\d-\d\d')
mo2=heroRegex2.search("9876-54 456-78-90  3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34")
print(mo2.group())

输出结果为:

9876-54

我们也可以将它与上面说的括号混合使用:

batRegex=re.compile(r'Bat(man|mobile|copter|bat)')
mo=batRegex.search("Batmobile lost a wheel")
print(mo.group())
print(mo.group(1))

运行结果:

Batmobile
mobile

上面就实现对多个拥有相同的前缀如何只指定一次前缀来指定几种可选的模式来让正则表达式来匹配。

用问号实现可选匹配

有时候,一些文本我们在匹配的时候希望它存在,但没有其实也可以接受,这时候我们可以使用字符**?**来表示前面的分组在该模式中是可选的,如下面的这一串代码:

answerFinder=re.compile(r'Bat(wo)?man')
mo=answerFinder.search("The Batman")
mo1=answerFinder.search("The Batwoman")
print("the mo:{}\nthe mo1:{}".format(mo.group(),mo1.group()))

输出为:

the mo:Batman
the mo1:Batwoman

如上**(wo)?表示wo是可选,无论是Batman还是Batwoman**都可以匹配成功

利用*匹配零次或多次

表示匹配0次或多次,即****前面的分组可以出现任意次,示例;

answerFinder=re.compile(r'Bat(wo)*man')
mo=answerFinder.search("The Batman")
mo1=answerFinder.search("The Batwowowowowoman")
print("the mo:{}\nthe mo1:{}".format(mo.group(),mo1.group()))

输出:

the mo:Batman
the mo1:Batwowowowowoman

利用加号匹配一次或多次

相比较于的0次或多次,+则是要求分组必须要出现一次,其余与基本一致。

利用{}匹配特定次数

如果想要一个分组重复指定次数,就在正则表达式中该分组后面跟上**{}**包围的数字。例如(\d){4}表示将匹配四个整数

除了在**{}**填入一个数字以外,还可以指定一个范围:

# 匹配两次
answerFinder=re.compile(r'Bat(wo){2}man')
#匹配至少两次
answerFinder=re.compile(r'Bat(wo){2,}man')
#匹配至多两次
answerFinder=re.compile(r'Bat(wo){,2}man')

示例:

phoneNumber1=re.compile(r'(\d){3}-(\d){3}-(\d){4}')
mo=phoneNumber1.search("My name is 147-907-7916")
print("The phone number:{}".format(mo.group()))

输出为:

The phone number:147-907-7916

贪心与非贪心匹配

如果我们匹配字符串**”hahahahaha“** (ha{3,5)可以匹配3个,4个或5个实例,但是当我们去匹配的时候就会发现只会返回hahahahaha,这是因为python的正则表达式默认是**”贪心“的,这表示有多个结果的时候,会默认选择尽可能长的字符串,但我们也可以在{}后面加上?**让其变为非贪心模式,示例如下:

answerFinder1=re.compile(r'(ha){3,5}')
answerFinder2=re.compile(r'(ha){3,5}?')
mo1=answerFinder1.search("hahahahaha")
mo2=answerFinder2.search("hahahahaha")
print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))

输出为:

the mo1:hahahahaha
the mo2:hahaha

findall()方法

除了search()方法,Regex对象还有一个findall()方法,search会返回一个Match对象,包含被查找字符串中”第一次“匹配的文本;而findall()方法将返回一组字符串,包含被查找字符串里面所有匹配文本,而且它返回的不是一个Match对象,而是一个字符串列表(如果在正则表达式中没有分组),如果有分组,那返回的就是一个元组的列表,其中的项就是正则表达式中每个分组的匹配字符串,如下:

heroRegex2=re.compile(r'\d\d\d\d-\d\d')
mo2=heroRegex2.findall("9876-54 456-78-90  3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34")
print(mo2)
heroRegex2=re.compile(r'(\d\d\d\d)-(\d\d)')
mo1=heroRegex2.findall("9876-54 456-78-90  3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34")
print(mo1)

输出为:

['9876-54', '3456-78', '3456-78', '1234-56']
[('9876', '54'), ('3456', '78'), ('3456', '78'), ('1234', '56')]

创建自己的字符分类

在前面我们介绍了常见的匹配字符,但是我们不得不承认这些字符的范围太过宽泛,我们可以使用方括号来定义自己的字符分类,例如我们可以使用**[aoeiuAOEIU]**来匹配所有的元音字符 ,

vowelRegx=re.compile(r'[aeiouAEIOU]')
mo=vowelRegx.findall("The quick brown fox jumped over the lazy dog")
print(mo)

输出:

['e', 'u', 'i', 'o', 'o', 'u', 'e', 'o', 'e', 'e', 'a', 'o']

插入字符^与美元字符$

我们可以在正则表达式开始处使用^,表示匹配必须发生在被查找文本开始处。同样的,我们可以在正则表达式的末尾加上, 表示必须以这个正则表达式的模式结束,我们可以同时使 用 和 ,表示必须以这个正则表达式的模式结束,我们可以同时使用^和,表示必须以这个正则表达式的模式结束,我们可以同时使,表示整个字符串必须匹配该模式,而不是字符串的某个子集,示例如下:

beginwithHello=re.compile(r'^Hello')
mo1=beginwithHello.search("Hello,World")
mo2=beginwithHello.search("Python,Hello")
print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))
endwithHello=re.compile(r'Hello$')
mo1=endwithHello.search("Hello,World")
mo2=endwithHello.search("Python,Hello")
print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))

我们会发现会报错

Traceback (most recent call last):
  File "E:\python\typora源码放置处(python)\正则表达式.py", line 76, in <module>
    print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))
                                                      ^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'group'

修改一下

beginwithHello = re.compile(r'^Hello')
mo1 = beginwithHello.search("Hello,World")
mo2 = beginwithHello.search("Python,Hello")
print("the mo1:{}\n".format(mo1.group()))
endwithHello = re.compile(r'Hello$')
mo2 = endwithHello.search("Python,Hello")
print("the mo2:{}\n".format(mo2.group()))

输出为

the mo1:Hello
the mo2:Hello

通配字符

在正则表达式中,**.**字符称为“通配字符”它可以匹配除了换行符以外的所有字符。

利用点-星匹配所有字符

有时候想要匹配所有字符串,例如假定想要匹配字符串First Name,接下来是任意文本,然后Last Name,然后还是任意文本,示例:

nameRegex=re.compile(r'First Name:(.*) Last Name:(.*)')
mo=nameRegex.search("First Name:John Last Name:Doe")
print(mo.group(1))
print(mo.group(2))

输出为:

John
Doe

非贪心匹配

nonggreedyRegex=re.compile(r'<.*?>')
mo=nonggreedyRegex.search("<To be, or not to be> is a question")
print(mo.group())
<To be, or not to be>

用句号字符匹配换行符

点-星可以匹配换行符外的所有字符,当我们传入re.DOTALL作为**re.compile()**的第二个参数,继而让句点字符匹配所有字符,包括换行符,示例如下:

nonggreedyRegex1=re.compile(r'.*')
mo1=nonggreedyRegex1.search("To be, or not to be> is a question\nFirst Name:John Last Name:Doe\nThe quick brown fox jumped over the lazy dog")
print(mo1.group())
nonggreedyRegex2=re.compile(r'.*')
mo1=nonggreedyRegex2.search("To be, or not to be> is a question\nFirst Name:John Last Name:Doe\nThe quick brown fox jumped over the lazy dog")
print(mo2.group())

输出:

To be, or not to be is a question
To be, or not to be is a question
First Name:John Last Name:Doe
The quick brown fox jumped over the lazy dog

不区分大小

通常,正则表达式在匹配的时候会注意文本的大小写,而不关心它们的大小写的话,可以传入re.I或者re,IGNOREVASE作为**re.compile()**的第二个参数,示例:

robocup=re.compile(r'Robocup',re.I)
mo1=robocup.search("Robocup is a sport")
mo2=robocup.search("ROBOCUP is a sport")
mo3=robocup.search("ROboCUp is a sport")
print("{},{},{}".format(mo1.group(),mo2.group(),mo3.group()))

输出为:

Robocup,ROBOCUP,ROboCUp

用sub()方法替换字符串

正则表达式不仅可以找到文本模式,而且还能用新的文本替换掉这些模式,Regex对象的sub方法需要接受两个参数:

  1. 一个用来替换发现的匹配的字符串
  2. 正则表达式

示例:

nameRegex = re.compile(r'Agent\w+')
nameRegex.sub("fengxu", "Agent Alice gave the secret documents to Agent Bob")

有时候我们需要匹配文本表示作为替换字符串的一部分,在sub方法的第一个参数中,可以输入**\1,\2,\3表示在替换中输入分组1、2、3**的文本,示例:

agentNameRegex=re.compile(r'Agent (\w)\w*')
result=agentNameRegex.sub(r'\1****','Agent Alice told Agent Carol that Agent Eve knew Agent Bob was double agent')
print(result)

输出为:

A**** told C**** that E**** knew B**** was double agent

管理复杂的正则表达式

如果匹配的文本模式很简单,那么使用简单的正则表达式就足够了,但是匹配复杂的文本模式 ,正则表达式可能很长很复杂,这时候我们就要引用空白符与注释了,这时候我们可以输入re.VERBOSE作为第二个参数,如下:

phoneRegex=re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4}(\S*(ext|x|ext.)\s*\d{2.5})?)')
phoneRegex2=re.compile('''(
    (\d{3}|\(\d{3}\))?           #area code
    (\s|-|\.)?                   #separator
    \d{3}                       #first 3 digits
    (\s|-|\.)                   #separator
    \d{4}                       # last digits
    (\S*(ext|x|ext.)\s*\d{2.5})?# extension
)''',re.VERBOSE)
相关文章
|
15天前
|
搜索推荐 Python
Python上下文管理器DIY指南:从入门到精通,轻松驾驭资源管理
【7月更文挑战第6天】Python的上下文管理器是资源管理的利器,简化文件操作、网络连接等场景。通过定义类及`__enter__`、`__exit__`方法,可自定义管理器,如示例中的`MyContextManager`,实现资源获取与释放。使用with语句,提升代码可读性和维护性,不仅用于基本资源管理,还可扩展到事务控制、自动重试等高级应用,让编程更加高效和灵活。
29 0
|
17天前
|
机器学习/深度学习 数据采集 数据可视化
Python数据分析入门涉及基础如Python语言、数据分析概念及优势。
【7月更文挑战第5天】Python数据分析入门涉及基础如Python语言、数据分析概念及优势。关键工具包括NumPy(数组操作)、Pandas(数据处理)、Matplotlib(绘图)、Seaborn(高级可视化)和Scikit-learn(机器学习)。流程涵盖数据获取、清洗、探索、建模、评估和展示。学习和实践这些将助你有效利用数据。
|
13天前
|
安全 Java 调度
「Python入门」Python多线程
1. **线程与进程区别**:线程共享内存,进程独立;线程启动快,多线程效率高于多进程。 2. **多线程使用**:直接使用Thread类,通过`target`指定函数,`args`传递参数;或继承Thread,重写`run`方法。 3. **守护线程**:设置`setDaemon(True)`,主线程结束时,守护线程一同结束。 4. **join线程同步**:主线程等待子线程完成,如`t.join()`。 5. **线程锁**(Mutex):防止数据竞争,确保同一时间只有一个线程访问共享资源。 6. **RLock(递归锁)**:允许多次锁定,用于需要多次加锁的递归操作。
19 1
「Python入门」Python多线程
|
13天前
|
数据采集 XML JSON
「Python入门」Python代码规范(风格)
**Python编码规范摘要** - 编码:使用UTF-8编码,文件开头可声明`# -- coding: utf-8 --`。 - 分号:避免在行尾使用,不用于分隔命令。 - 行长:不超过80字符,长表达式可使用括号换行。 - 缩进:使用4个空格,禁止混用tab。 - 注释:行注释始于`#`和空格,块注释和文档注释遵循特定格式。 - 空行:函数和类定义间用2空行,方法间1空行,内部适当空行。 - 空格:运算符两侧各空一格,逗号后空格,括号内不空格。 - 命名:模块小写,变量下划线分隔,类驼峰式,布尔变量前缀`is_`。 - 引号:保持一致性,可使用单引号或双引号。
16 1
「Python入门」Python代码规范(风格)
|
18天前
|
测试技术 Python
|
3天前
|
存储 分布式计算 索引
Python函数式编程入门窥探
Python本身不是一门函数式编程语言,但是它参考了一些函数式编程语言很好的地方,除了可以写出更可读的代码外。还能用它来实现一些特定功能,本身也提供了强大的注解系统和函数和对象之间的灵活调用。
|
5天前
|
算法 数据挖掘 计算机视觉
Python并查集实战宝典:从入门到精通,让你的数据结构技能无懈可击!
【7月更文挑战第17天】并查集,如同瑞士军刀,是解决元素分组问题的利器,应用于好友关系、像素聚类、碰撞检测和连通性分析等场景。本文从基础到实战,介绍并查集的初始化、查找与路径压缩、按秩合并,以及在Kruskal算法中的应用。通过并查集,实现高效动态集合操作,对比哈希表和平衡树,其在合并与查找上的性能尤为突出。学习并查集,提升算法解决复杂问题的能力。
|
5天前
|
监控 数据可视化 定位技术
这本书凭什么得到ChatGPT认可,评价其为最值得读的Python入门书
在当今这个飞速发展且高度数字化的时代,编程已经成为一项至关重要的技能,其重要性愈发凸显。而 Python 作为一种在众多领域都有着广泛应用且相对来说较为容易学习的编程语言,顺理成章地成为了许多编程初学者的热门选择。 就在昨天,图灵君在浏览豆瓣的时候突然被这样一条评论闪到,一位网友说:“ChatGPT 推荐给我的入门书”。我想这书莫不是口碑爆棚、备受好评的蟒蛇书《Python编程:从入门到实践(第3版)》吧!仔细一看还真是!
|
17天前
|
数据采集 编译器 iOS开发
【Python从入门到精通】(一)就简单看看Python吧
【Python从入门到精通】(一)就简单看看Python吧
34 8
|
13天前
|
SQL 关系型数据库 MySQL
「Python入门」python操作MySQL和SqlServer
**摘要:** 了解如何使用Python的pymysql模块与MySQL数据库交互。首先,通过`pip install pymysql`安装模块。pymysql提供与MySQL的连接功能,例如创建数据库连接、执行SQL查询。在设置好MySQL环境后,使用`pymysql.connect()`建立连接,并通过游标执行SQL(如用户登录验证)。注意防止SQL注入,使用参数化查询。增删改操作需调用`conn.commit()`来保存更改。pymssql模块类似,但导入和连接对象创建略有不同。
15 0
「Python入门」python操作MySQL和SqlServer