【Python高级370】Python高级语法
- with语句:上下文管理器
- 上文管理器:open
- 下文管理器:close
- yield生成器(掌握两种创建方式)
- 类似推导式结构
- 大大降低程序能耗
- 深浅拷贝
- 面试考的多:深拷贝和浅拷贝的区别
- 正则表达式
371-with上下文管理器
写在前面
老的文件操作方法:
# 写入模式打开文件
f = open("1.txt", "w")
# 写入文件内容
f.write("Ikun")
# 关闭文件
f.close()
文件在使用完后必须关闭,因为文件对象会占用操作系统资源,并且操作系统同一时间能打开的文件数量也是有限的(linux内核信息定义的最大打开文件数是1024)
老方法存在的问题:
#1、以读的方式打开文件
f = open("1.txt", "r") # 只读模式
#2、写入文件内容
f.write("Ikun") # 但是写入文件
#3、关闭文件
f.close()
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用,这里就会造成资源浪费(会占用系统资源)
对此问题,可以用try…except…语句来解决
但是最好的方案是使用with语句(高级语法,代码简化)
with语句和上下文管理器
上下文管理器和with语句的定义
- 上下文管理器:上下文管理器是Python中的一个协议,用于管理资源的生命周期,例如文件的打开和关闭、数据库的连接和断开等
- with语句:with语句用于管理上下文管理器,在文件操作中,with语句可以自动管理文件的打开和关闭,避免忘记关闭文件导致资源泄露
Python提供了 with 语句的写法,既简单又安全。
文件操作的时候使用with语句可以自动调用关闭文件操作,即使出现异常也会自动关闭文件操作。
使用with方法实现文件操作,如下所示:
# 1、以写的方式打开文件
with open('1.txt', 'w') as f:
# 2、读取文件内容
f.write('Ikun')
上下文管理器
当使用with语句后,就会自动创建上文管理器(__enter()__
)和下文管理器(__exit()__
)
__enter()__
:f = open("1.txt", "w")
__exit()__
:f.close()
372-Python生成器
生成器是什么
根据程序设计者制定的规则循环生成数据,当条件不成立时则生成数据结束
作用
数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存,降低程序运行消耗时间。
创建生成器的方式
① 推导式 生成器
② yield 关键字生成器
推导式生成器
与列表推导式类似,只不过生成器推导式使用小括号。
[[109-推导式(数据容器)]]
# 创建生成器
my_generator = (i * 2 for i in range(5))
print(my_generator)
(i * 2 for i in range(5))
是生成器
[i * 2 for i in range(5)]
是直接生成现成的列表
(i * 2 for i in range(5))
本质是一个对象,其中没有02468这些数据,只有数据生成的规则
如果打印my_generator
,那么打印的只是这个对象的内存地址
# next 获取生成器下一个值
value = next(my_generator)
print(value)
- 使用next()函数获取生成器的元素:
生成器中有一个关键的函数:next(),每调用一次就会根据规则创建一个元素,然后向后移动,再次调用next()函数就会再生成一个元素
# 遍历生成器
for value in my_generator:
print(value)
- for 循环遍历生成器中的每一个值
yield生成器
yield生成器是啥
重点理解yield生成器的执行流程
yield生成器结构:
- 定义一个函数
- 函数内部存在一个yield关键字
yield 关键字生成器的特征:在def函数中具有yield关键字
当函数和yield组合在一起就叫做生成器,这个函数就不是函数了,就转变成了对象
代码演示:
用next函数获取生成器的元素
def generator(n):
for i in range(n):
print('开始生成数据')
yield i # 暂时可以把yield当成return理解,每次遇到yield,生成器就相当于执行一次(生成循环执行到yield后就暂停,后面在调用就从这里继续执行,意思就是'完成一次数据生成'这句话会在第二次生成的时候打印)
print('完成一次数据生成')
# 使用时,由于生成器需要传递参数,所以通常将其赋予给某个变量
g = generator(5)
# 如果print(generator(5)),输出的是对象的内存地址
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))-----> 正常
print(next(g)) -----> 报错
Traceback (most recent call last):
File "/Users/cndws/PycharmProjects/pythonProject/demo.py", line 14, in <module>
print(next(g))
StopIteration(到达边界)
但是这里执行第六次next()会把'完成一次数据生成'这句打印出来
用for循环遍历生成器的元素
for循环的好处:可以避免上面出现的到达边界的错误
def generator(n):
for i in range(n):
print('开始生成...')
yield i
print('完成一次...')
g = generator(5)
for i in g:
print(i)
使用while循环+try…except语句获取生成器元素
这样同样可以避免到达边界的错误
def generator(n):
for i in range(n):
print('开始生成...')
yield i
print('完成一次...')
g = generator(5)
while True:
try:
print(next(g))
except StopIteration:
break
yield关键字和return关键字
如果不太好理解yield
,可以先把yield
当作return
的同胞兄弟来看,他们都在函数中使用,并履行着返回某种结果的职责。
这两者的区别是:
有return
的函数直接返回所有结果,程序终止不再运行,并销毁局部变量;
def example():
x = 1
return x
example = example()
print(example)
而有yield
的函数则返回一个可迭代的 generator(生成器)对象,你可以使用for循环或者调用next()方法遍历生成器对象来提取结果。
def example():
x = 1
y = 10
while x < y:
yield x
x += 1
example = example()
print(example)
生成器的现实应用案例
斐波那契数列:数学中的一个概念,是一组有规律的数字!
1 1 2 3 5 8 13 21 34 55 …
规律:
隐藏了一个元素 => 0
第1个元素为1
第2个元素为1,第2个元素为1,实际上是由0 + 1 = 隐藏元素 + 第一个元素
第3个元素为2 = 1 + 1,第3个元素值 = 第2个元素值 + 第1个元素值
第4个元素为3 = 2 + 1,第4个元素值 = 第3个元素值 + 第2个元素值
刚好满足:数据量比较大,而且还要经过大量的计算 => 符合生成器的使用规则
0 1 1 2 3 5 8 13 21 34 55 …
a变量代表,斐波那契数列中前一个元素,比如a可以代表0
b变量代表,斐波那契数列中后一个元素,比如b可以代表1
a,b是相邻的两个元素
def fib(max): # 求max位斐波那契数列 => 前max位斐波那契数列的每个值
n, a, b = 0, 0, 1
while n < max:
yield b # 第一次循环,弹出一个1
# 想个办法,让第二次yield b,弹出一个1,第三次yield b,弹出一个2
# a = 0, b = 1
a, b = b, a + b
# a = 1, b = 1
n += 1
result = fib(50)
for i in result:
print(i)
使用生成器的注意事项:
① 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
② 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration 异常,表示停止迭代异常
③ while 循环内部没有处理异常操作,需要手动添加处理异常操作
④ for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐大家使用。
373-Python中的深浅拷贝
Python中的深浅拷贝
[!NOTE] 复习几个概念
- 变量:是一个系统表的元素,拥有指向对象的连接空间
- 对象:被分配的一块内存,存储其所代表的值
- 引用:是自动形成的从变量到对象的指针
- 类型:属于对象,而非变量
- 不可变对象:一旦创建就不可修改的对象,包括数值类型、字符串、布尔类型、元组
(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)
- 可变对象:可以修改的对象,包括列表、字典、集合
(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)
0.赋值
赋值: 只是复制了新对象的引用,不会开辟新的内存空间。
并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。
1.浅拷贝
定义(只有最外层容器是新的,内部元素都是共享的)
[!NOTE]
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(分支)。
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
浅拷贝: 创建新对象,其内容是原对象的引用。
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
Python浅拷贝实验
可变数据类型的浅拷贝
栗子1
import copy
a = [1,2,3]
b = a # 传统赋值
c = a[:] # 切片(底层就是浅拷贝)
d = copy.copy(a) # 浅拷贝
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"d的id: {id(d)}")
print(f"a[0]的id: {id(a[0])}")
print(f"b[0]的id: {id(b[0])}")
print(f"c[0]的id: {id(c[0])}")
print(f"d[0]的id: {id(d[0])}")
控制台输出:
a的id: 2596354113728
b的id: 2596354113728
c的id: 2596354069056
d的id: 2596355427712
a[0]的id: 140716802828728
b[0]的id: 140716802828728
c[0]的id: 140716802828728
d[0]的id: 140716802828728
栗子2
import copy
# 创建对象
a = [1, 2, 3]
b = [4, 5, 6]
data = [a, b]
# 传统赋值
c = data
# 浅拷贝
d = copy.copy(data)
# 验证
print(f"data的id: {id(data)}")
print(f"c的id: {id(c)}") # 与data相同
print(f"d的id: {id(d)}") # 与data不同
print(f"\ndata[0]的id: {id(data[0])}")
print(f"d[0]的id: {id(d[0])}") # 与data[0]相同
控制台输出:
data的id: 2024771407424
c的id: 2024771407424
d的id: 2024771282944
data[0]的id: 2024770027712
d[0]的id: 2024770027712
不可变数据类型的浅拷贝
栗子1
import copy
a = (1,2,3)
b = a # 传统赋值
c = a[:] # 切片(底层就是浅拷贝)
d = copy.copy(a) # 浅拷贝
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"d的id: {id(d)}")
print(f"a[0]的id: {id(a[0])}")
print(f"b[0]的id: {id(b[0])}")
print(f"c[0]的id: {id(c[0])}")
print(f"d[0]的id: {id(d[0])}")
控制台输出:
a的id: 1958923423680
b的id: 1958923423680
c的id: 1958923423680
d的id: 1958923423680
a[0]的id: 140716802828728
b[0]的id: 140716802828728
c[0]的id: 140716802828728
d[0]的id: 140716802828728
栗子2
import copy
# 创建对象
a = (1, 2, 3)
b = (4, 5, 6)
data = (a, b)
# 传统赋值
c = data
# 浅拷贝
data_copy = copy.copy(data)
# 验证所有引用
print("验证外层元组:")
print(f"data的id: {id(data)}")
print(f"c的id: {id(c)}") # 与data相同
print(f"data_copy的id: {id(data_copy)}") # 与data相同
print("\n验证内部元素:")
print(f"data[0]的id: {id(data[0])}")
print(f"c[0]的id: {id(c[0])}") # 与data[0]相同
print(f"data_copy[0]的id: {id(data_copy[0])}") # 与data[0]相同
对于不可变类型(如元组),copy.copy()不会创建新的对象,这是因为不可变对象是安全的,不需要创建副本。
对于元组这样的不可变类型来说,浅拷贝和简单赋值的效果是完全一样的
控制条输出:
验证外层元组:
data的id: 1714974555456
c的id: 1714974555456
data_copy的id: 1714974555456
验证内部元素:
data[0]的id: 1714974379968
c[0]的id: 1714974379968
data_copy[0]的id: 1714974379968
浅拷贝有三种形式:
- 切片操作(底层用copy.copy实现)
- 工厂函数(list())
- copy模块中的copy函数。
浅拷贝的笔试题
a = [1, 3, 5, [7, 9]]
b = a[:]
a[1] = 2
a[3][1] = 10
print(b) # 问b会打印出什么来
答案:[1, 3, 5, [7, 10]]
2.深拷贝
定义
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
[!NOTE]
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)
- 拷贝第一层级的对象属性或数组元素
- 递归拷贝所有层级的对象属性和数组元素
- 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
Python深拷贝实验
可变数据类型的深拷贝
import copy
a = [1, 2, 3, [4, 5, 6]]
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
控制台输出:
a的id: 1467343770176
b的id: 1467345194368
a[3]的id: 1467343814848
b[3]的id: 1467345070464
结论:
- 对于简单的可变数据类型,深拷贝可以对对象进行完全拷贝,生成一块独立的内存空间,两个变量之间没有任何关系
- 对于复杂可变数据类型,深拷贝会递归拷贝所有层级的对象属性和数组元素,生成一块独立的内存空间,且两个变量之间没有任何关系
不可变数据类型的深拷贝
栗子1
import copy
a = (1, 2, 3)
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
控制台输出:
a的id: 2423245393856
b的id: 2423245393856
栗子2
import copy
a = (1, 2, 3, (4, 5, 6))
b = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
控制台输出:
a的id: 2632243691280
b的id: 2632243691280
a[3]的id: 2632243695552
b[3]的id: 2632243695552
结论:
对于不可变数据类型,深拷贝也只能拷贝对象的引用关系,结果就是指向相同内存空间
3.深浅拷贝的特殊情况
可变类型嵌套不可变类型
实验:
import copy
a = [1, 2, 3, (4, 5, 6)]
b = copy.copy(a)
c = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
print(f"c[3]的id: {id(c[3])}")
控制台输出:
a的id: 1753060524224
b的id: 1753060479552
c的id: 1753061903744
a[3]的id: 1753061609408
b[3]的id: 1753061609408
c[3]的id: 1753061609408
结论:外层可变类型可以生成内存空间完全拷贝,但是内层对象是不可变类型,只能拷贝引用关系
不可变类型嵌套可变类型⭐
实验:
import copy
a = (1, 2, 3, [4, 5, 6])
b = copy.copy(a)
c = copy.deepcopy(a)
print(f"a的id: {id(a)}")
print(f"b的id: {id(b)}")
print(f"c的id: {id(c)}")
print(f"a[3]的id: {id(a[3])}")
print(f"b[3]的id: {id(b[3])}")
print(f"c[3]的id: {id(c[3])}")
控制台输出:
a的id: 2603735950784
b的id: 2603735950784
c的id: 2603735950864
a[3]的id: 2603734778048
b[3]的id: 2603734778048
c[3]的id: 2603736157568
特殊结论:对于深拷贝,由于内层是可变类型需要拷贝,导致外层不可变类型也被拷贝,导致整体都可以进行完全拷贝
深浅拷贝总结
赋值: 值相等,地址相等
copy浅拷贝:值相等,地址不相等
deepcopy深拷贝:值相等,地址不相等
374-正则表达式
正则表达式(字符串的匹配规则)
在实际开发过程中经常会有查找符合某些复杂规则的字符串的需要
比如:邮箱、图片地址、手机号码等
这时候想匹配或者查找符合某些规则的字符串就可以使用正则表达式了
什么是正则表达式
正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。
模式:一种特定的字符串模式,这个模式是通过一些特殊的符号组成的。
某种:也可以理解为是一种模糊匹配。
精准匹配:select * from blog where title=‘python’;
模糊匹配:select * from blog where title like ‘%python%’;
正则表达式并不是Python所特有的,在Java、PHP、Go以及JavaScript等语言中都是支持正则表达式的。
正则表达式的用途
① 数据验证(表单验证、如手机、邮箱、IP地址)
② 数据检索(数据检索、数据抓取)
③ 数据隐藏(100****0086 小黑子)
④ 数据过滤(论坛敏感关键词过滤)
re模块 for 正则表达式
什么是re模块
在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用一个re模块(regexp:正则模块)
re模块使用方法
三步
# 第一步:导入re模块
import re
# 第二步:使用match方法进行匹配操作
# match方法只能匹配开头,如果要匹配任意位置可以用findall(),findall返回的是一个列表
result = re.match(pattern正则表达式, string要匹配的字符串, flags=0)
# 第三步:如果数据匹配成功,使用group方法来提取数据
result.group()
re模块的相关方法
- re.match(pattern, string, flags=0)
- 从字符串的起始位置匹配,如果匹配成功则返回匹配内容(返回re正则对象,需要通过
result.group(分组编号)
获取内容), 否则返回None
- 从字符串的起始位置匹配,如果匹配成功则返回匹配内容(返回re正则对象,需要通过
- re.search()
- 匹配满足正则表达式的结果,没有位置限制,在哪都可以,但是只匹配第一个满足条件的结果(返回re正则对象,需要通过
result.group(分组编号)
获取内容)
- 匹配满足正则表达式的结果,没有位置限制,在哪都可以,但是只匹配第一个满足条件的结果(返回re正则对象,需要通过
- re.findall(pattern, string, flags=0)
- 扫描整个串,返回所有与pattern匹配的列表
- 注意: 如果pattern中有分组则返回与分组匹配的列表(无法获取分组中的内容)
- 举例:
re.findall("\d","chuan1zhi2") >> ["1","2"]
- re.finditer(pattern, string, flags)
- 功能与上面findall一样,不过返回的是迭代器
- 有满足正则表达式的结果(所有)没有位置限制,finditer()不仅可以匹配到整个正则匹配到的结果,其还可以专门用于获取分组中得到的数据,可以使用
result.group(分组编号)
获取分组内容
- re.split()
- 按照指定字符对字符串进行分割操作,也支持正则表达式,返回列表
- 举例:
re.split("\d","chuan1zhi2") >> ["chu", "zhi"]
,解释:按照数字对字符串进行分割,返回列表
- re.sub()
- 替换字符串中所有匹配正则表达式的字符串,返回替换后的字符串
- 举例:
re.sub("\d","*","chuan1zhi2") >> "chuan*zhi*"
,解释:将字符串中的数字替换为*
- re.compile()
- 将正则表达式编译成正则表达式对象,用于提高效率
- 举例:
re.compile("\d")
,解释:将正则表达式编译成正则表达式对象
正则表达式基础
正则编写三步走:查什么、查多少、从哪查
正则表达式通常是由两部分数据组成的:普通字符 与 元字符
普通字符:0123456789abcd@…
元字符:正则表达式所特有的符号 => [0-9],^,*,+,?
1、查什么
代码 | 功能 |
---|---|
. | 匹配任意某1个字符(除了\n)(万能字符) |
[ ] | 匹配[ ]中列举的某1个字符,专业名词 => 字符簇 |
[^指定字符] | 匹配除了指定字符以外的其他某个字符(取反),^专业名词 => 托字节 |
\d | 匹配数字,即0-9 |
\D | 匹配非数字,即不是数字 |
\s | 匹配空白,即 空格,tab键 |
\S | 匹配非空白 |
\w | 匹配非特殊字符,即a-z、A-Z、0-9、_ |
\W | 匹配特殊字符,即非字母、非数字、非下划线 |
字符簇常见写法:
① [abcdefg] 代表匹配abcdefg字符中的任意某个字符(1个)
② [aeiou] 代表匹配a、e、i、o、u五个字符中的任意某个字符
③ [a-z] 代表匹配a-z之间26个字符中的任意某个
④ [A-Z] 代表匹配A-Z之间26个字符中的任意某个
⑤ [0-9] 代表匹配0-9之间10个字符中的任意某个
⑥ [0-9a-zA-Z] 代表匹配0-9之间、a-z之间、A-Z之间的任意某个字符
例如:
str = '22afff3f4g'
result = re.match('[0-9]', str)
print(result.group())
字符簇 + 托字节结合代表取反的含义:
① [^aeiou] 代表匹配除了a、e、i、o、u以外的任意某个字符
② [^a-z] 代表匹配除了a-z以外的任意某个字符
\d 等价于 [0-9], 代表匹配0-9之间的任意数字
\D 等价于 [^0-9],代表匹配非数字字符,只能匹配1个
str = '22afff3f4g'
result = re.match('\d', str)
print(result.group())
str = 'czc0927@XXGG.com$'
result = re.match('\W', str)
print(result.group())
这个代码能够匹配出’$'这个特殊字符
2、查多少
代码 | 功能 |
---|---|
* | 匹配前一个字符出现任意次:src="(.*)" :匹配src="任意字符"任意次 |
+ | 匹配前一个字符出现至少1次:\w+ :常规字符出现至少一次 |
? | 匹配前一个字符出现1次或者0次:10? :匹配10或者1 |
{m} | 匹配前一个字符出现m次;\d{11} :匹配11位手机号码 |
{m,} | 匹配前一个字符至少出现m次,\w{3,} ,代表前面这个字符最少要出现3次,最多可以是无限次 |
{m,n} | 匹配前一个字符出现从m到n次,\w{6,10} ,代表前面这个字符出现6到10次 |
基本语法:
正则匹配字符.
或\w
或\S +
跟查多少
如\w{6, 10}
如.*
,匹配前面的字符出现0次或多次
实际栗子1:
匹配连续的三个数字:
str = '123aaa283Ikun'
result = re.match('\d{3}', str)
print(result.group())
控制台输出:
123
实际栗子2:
匹配多个字符:
str = '123aaa283Ikun.txt'
result = re.match('.*', str) # 匹配任意字符出现0次或多次
print(result.group())
控制台输出:
123aaa283Ikun.txt
实际栗子3:
匹配手机号码是否合理:
str = '13812345678'
result = re.match('1[3-9]\d{9}', str)
print(result.group())
if result:
print('匹配成功')
else:
print('匹配失败')
这个代码存在问题,当电话号码是13812345678时,会匹配成功,但是当电话号码是1381234567890时,也会匹配成功,这显然是不合理的。所以需要使用^
和$
(从哪查)来限制电话号码的长度。
3、从哪查
默认都是从头开始查,现在指定从哪开始查,查到哪里结束
代码 | 功能 |
---|---|
^ | 匹配以某个字符串开头 |
$ | 匹配以某个字符串结尾 |
栗子:
匹配手机号码完善版
str = '13812345678'
result = re.match('^1[3-9]\d{9}$', str) # 添加限制只能匹配11位手机号码
print(result.group())
if result:
print('匹配成功')
else:
print('匹配失败')
正则表达式高级
子表达式(分组)
在正则表达式中,通过一对圆括号括起来的内容,我们就称之为==“子表达式”==。
re.search(r'\d(\d)(\d)', 'abcdef123ghijklmn')
注意:Python正则表达式前的 r 表示原生字符串(rawstring),该字符串声明了引号中的内容表示该内容的原始含义,避免了多次转义造成的反斜杠困扰。
正则表达式中\d\d\d中,(\d)(\d)就是子表达式,一共有两个()
圆括号,则代表两个子表达式
说明:findall方法,如果pattern中有分组则返回与分组匹配的列表,所以分组操作中不适合使用findall方法,建议使用search(匹配一个)或finditer(匹配多个)方法。
捕获
当正则表达式在字符串中匹配到相应的内容后,计算机系统会自动把子表达式所匹配的到内容放入到系统的对应缓存区中
栗子:
import re
# 匹配字符串中连续出现的两个相同的单词
str1 = 'abcdef123ghijklmn'
result = re.search(r'\d(\d)(\d)', str1)
print(result.group()) # 123(获取正则匹配到的所有内容)
print(result.group(1)) # 2(获取1号分组匹配到的内容)
print(result.group(2)) # 3(获取2号分组匹配到的内容)
分组引用 反向引用(后向引用)
在正则表达式中,我们可以通过\n(n代表第n个缓存区的编号)来引用缓存区中的内容,我们把这个过程就称之为"反向引用"。
① 连续4个数字
re.search(r’\d\d\d\d, str1)
1234、5678、6789
② 连续的4个数字,但是数字的格式为1111、2222、3333、4444、5555效果?
re.search(r'(\d)\1\1\1, str1)
- 这里的r代表吧
\
反斜杠转义字符当作普通的字符处理 - 这里的
\1
代表第一组,也就是第一个括号的分组
选择匹配
正则表达式中可以使用|
匹配多个规则
案例:匹配字符串hellojava或hellopython
import re
str = 'hellojava, hellopython'
result = re.finditer(r'hello(java|python)', str) # 这里用finditer方法
if result:
for i in result:
print(i.group())
else:
print('未匹配到任何数据')
分组别名
把正则表达式中的分组起一个别名,方便后期查找和使用
代码 | 功能 |
---|---|
(?P) | 分组起别名 |
(?P=name) | 引用别名为name分组匹配到的字符串 |
主要用于HTML标签的匹配 |
案例:匹配 <book></book>
# 导入模块
import re
str1 = '<book></book>'
result = re.search(r'<(?P<mark>\w+)></(?P=mark)>', str1)
print(result.group())
?P<mark>
:给正则表达式起了一个别名,叫mark
?P=mark
:引用刚才定义的mark别名的正则表达式
扩展:使用正则工具箱
复杂的正则表达式很难写,用工具箱来代写
直接搜索引擎搜索就有很多
- 工具箱
- 常用表达式,带有python代码的
或者用AI来写
综合案例
①需求:在列表中[“apple”, “banana”, “orange”, “pear”],匹配apple和pear
import re
list1 = ["apple", "banana", "orange", "pear"]
# 吧列表转为字符串
str1 = str(list1)
# 使用正则表达式匹配apple和pear
result = re.finditer('(apple|pear)', str1)
if result:
for i in result:
print(i.group())
else:
print('未匹配到任何数据')
② 需求:匹配出163、126、qq等邮箱
转义字符:如果我们想把某个元字符转换为普通字符,可以使用\
反斜杠实现
import re
email = '1478670@qq.com, go@126.com, heima123@163.com'
result = re.finditer(r'\w+@(qq|126|163)\.com', email)
if result:
for i in result:
print(i.group())
else:
print('未匹配到任何数据')
③需求 : 匹配qq:10567这样的数据,提取出来qq文字和qq号码
import re
str1 = 'qq:913809'
result = re.split(r':', str1)
if result:
print(f'{result[0]}号:{result[1]}')
else:
print('未匹配到任何数据')
④需求:匹配出<html>hh</html>
import re
str1 = '<html>hh</html>'
result = re.search(r'<(?P<mark>\w+)>(?P=mark)</(?P=mark)>', str1)
print(result.group())
⑤需求:匹配出<html><h1>www.baidu.com</h1></html>
import re
str1 = '<html><h1>www.baidu.com</h1></html>'
result = re.match(r'<(\w+)><(\w+)>(.*)</\2></\1>', str1)
# 使用分组别名
# result = re.search(r'<(?P<mark>\w+)><(?P<mark2>\w+)>(?P=mark2)</(?P=mark)>', str1)
# print(result.group())
print(result.group(3)) # 获取第三个分组匹配到的内容:www.baidu.com
原文地址:https://blog.csdn.net/qq_25177949/article/details/144772346
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!