一、函数的多返回值:
之前我们的函数返回值每次都只能返回一个字面量或变量,那么如果有多个参数或者多个字面量等数据需要返回,那么我们需要如何去处理呢?
语法:return 参数一, 参数二, 参数三…
以下为示例:
# 定义了一个函数
def my_hi():
# 返回值返回了多种不同的数据类型
return 1, "hello", True
# 由于有三个返回值,所以我们需要用x,y,z这三个变量去接收他的返回值
x, y, z = my_hi()
print(f"取多返回值:{x}")
print(f"取多返回值:{y}")
print(f"取多返回值:{z}")
# 当我们使用一个变量去接收多个参数时,他的类型则为元组
res = my_hi()
print(f"使用一个变量去接收时,他的类型为:{type(res)},他的内容为:{res}")
输出结果:我们首先定义了函数,使用返回值依次返回多种不同的数据及数据类型,并使用了三个变量依次对应去接收他的参数返回值
如果我们使用一个函数去接收的话他则会获得一个元组类型的数据,这样我们也可以利用元组的一些方法对该数据做特殊的处理
总结:
1、如函数需返回多个返回值,则可以在return处依次将多个变量写入并用逗号分割。
2、接收多个返回值,那么我们可以在调用函数时用三个变量进行接收,需注意变量一对应返回值一,变量二是对应返回值二等等依次进行存储的。
3、多返回值是不限制数据类型的,可以混装多种数据类型
4、当我们使用一个参数去接收多个返回值,则会获得一个元组类型
二、函数多种参数传递:
定义函数时的形参分别有多种写法,可实现不同的功能。
- 1、位置参数:传统的写法,用于接收函数时的实参(入参)传递,必须按顺序填入,不可乱序
- 调用时语法:函数名(参数,参数)
- 2、关键字参数:调用函数写实参时,以键值对的方式对形参的数据进行传递,对形参的顺序无要求,可乱序
- 调用时语法:函数名(形参=参数,形参=参数)
- 3、缺省参数:可为某个形参设置默认值,在调用时实参如未指定数据,则使用预先设定好的默认值
- 定于函数语法:def 函数名(形参,形参=默认值)。等于默认值就是将次形参预先设定了默认值
- 4、位置不定长参数:不确定会有几个数据传入时,可使用该形参定义方式,一个形参可存储多个数据,也可理解为定义了一个元组类型的形参
- 定于函数语法:def 函数名(形参)。形参前加号等于该参数可传入多个数据元素
- 5、关键字不定长参数:不确定会有几个参数时,可以使用该形参定义方法,一个形参可存储多个参数和数据,由于他以键值对字典的方式进行存储的,所以可以理解为定义一个字典类型的形参
- 定于函数语法:def 函数名(**形参)。两个星号等于该参数可以以键值对的方式存储多个参数和数据(key: value)
以下示例演示
2.1、位置参数示例:
# 定义一个函数,分别有三个形参
def user_info(name, age, gender):
print(f"我叫{name},我今年{age}岁,我的性别是{gender}")
print("==============位置参数===============")
# 位置参数入参,也就是和之前
user_info("小美", 18, "女")
输出结果:该方式就是我们之前一直使用的方式,实参要与形参一一对应,否则你传入的参数则会到其他的参数位置去
2.2、关键字参数示例:
print("==============关键字参数===============")
# 关键字参数,以键值对的方式对参数进行传入,不依赖顺序,但是要按照形参依次赋予
user_info(age=18, gender='女', name="小美")
# 同时关键字和位置参数,可以混合使用
user_info("小明", age=18, gender="女")
输出结果:关键字参数其实就是在调用写实参时,以键值对的方式,形参=数据的方式将数据明确传递进去,该方式更清晰明了
同时关键字可以和位置参数混用,但是需注意位置参数必须要写在开头,由于位置参数写在开头那么他肯定就是对应着第一个形参,你位置参数对应之后的形参才可以使用关键字参数的方式去指定传入
2.3、缺省参数示例:
# 缺省参数
def user_info(name, age=18, gender='男'):
print(f"我叫{name},我今年{age}岁,我的性别是{gender}")
print("==============缺省参数===============")
# 当形参设置了默认值,我们在写实参的时候没给指定参数,则会使用默认值
user_info("小王")
# 当形参设置了默认值,我们在写实参的时候也给指定参数,则会使用我们指定的参数
user_info("小美", 20, '女')
输出结果:我们可以在定义函数形参时,给某个形参设置默认值,最后我们调用的时候。
如果没给该形参指定实参数据时,则该形参就会使用这个默认值。
如果给该形参指定实参数据时,则该形参就会使用我们指定的数据
2.4、位置不定长参数示例:
print("==============位置不定长参数===============")
# 不定长参数-元素不定长
def user_info(*user):
print(f"*user的类型为:{type(user)}")
print(f"*user的内容为:{user}")
user_info("小美", 18, "女")
输出结果:我们定义函数形参时使用了*user,代表他是一个位置不定长的形参,他可以一个形参接收多个数据
为什么他可以接收多个数据呢?可从下方结果看出,他其实是定义了一个元组类型的形参,每个数据都按一个元组的元素进行存储的,所以也可以理解为定义一个元组数据类型的形参
2.5、关键字不定长参数示例:
print("==============关键字不定长参数===============")
# 不定长参数-关键字不定长
def user_info(**user):
print(f"**user的类型为:{user}")
print(f"**user的内容为:{user}")
user_info(name="小美", age=18, gender="女")
输出结果:我们定义函数形参时使用了**user,代表他是一个关键字不定长的形参,他可以一个形参接收多个变量:数据
为什么他可以接收多个变量:数据呢?可以从下方结果看出,他其实是定义了一个字典类型的形参,每个变量:数据都按照字典的键值对方式进行存储的(key: value),所以我们在传实参时要自己定义你的这个key(变量名):value(数据)
这样我们在想想我们之前学的字典他是如何检索数据的,key检索value从而去做之后的一系列判断等等
三、函数作为参数传递:
这节我们需要了解函数如何作为参数传递,以及和之前的写法用法有什么区别
以下图以前常用的方式进行比较:调用时传入的是数据,函数内不确定是数据,+号明确的功能
而本次要说的是,我们要使用函数作为调用时的参数给过去
例如下图:我们调用函数时传入的实参不再是具体的数据或具体的某个变量等等数据类型,而是传入了一个add的函数,这个add的函数内做了具体的功能逻辑实现,就是接收两个数据进行相加,最终在实际调用的test_func这个函数接收的add(形参)他最终接收到的是我传入的add函数的计算逻辑,而add(1, 2)这个调用的是形参的add,但是这个形参接收到的是另一个函数add的逻辑,所以此时这个add的形参他不再是之前我们认识的变量或者数据了,他是一个相当于继承了add函数的一个逻辑函数,所以这里的写法看上去像是在调用函数,最终通过这个逻辑计算出1+2等于3
以上这个示例如果看了还是对这个概念比较模糊的话,以下还又一个示例,看起来要不那么迷糊
解析:有add两数相加功能实现的函数和cut两数相减的函数
调用test_func函数,传入add函数,函数内的func拥有了add函数的两束运算的功能逻辑
test_func函数体内,调用func形参(由于它拥有了add函数的功能,所以此刻你可以认为它就是一个函数),并具有固定的两个数值,add是相加,所以最终返回会得到一个:3
那么如果我此刻不想要相加了,我想要相减,那么我就直接把传入的实参换成cut,相减的功能函数,不就可以实现了
# 示例代码块一
def test_func(func):
result = func(1, 2)
return result
def add(x, y):
return x + y
def remove(x, y):
return x - y
print(test_func(remove))
# 示例代码块二
# func接收到一个函数逻辑
def test_func(func):
# 固定数值赋予函数逻辑进行处理
result = func(1, 2)
# 返回处理后的结果
return result
# 相加功能函数
def add(x, y):
return x + y
# 相减功能函数
def cut(x, y):
return x - y
# 传入函数代码逻辑
print(test_func(cut))
总结:之前我们是对一个形参传入的都是数据, 此刻我们传入的不再是数据,而是一个函数的代码逻辑
当我们有固定的值,但是不确定要以什么逻辑对固定值进行处理时,我们就可以使用此方式进行函数逻辑的传递,实现动态对两个固定值不同情况的处理
所以函数是可以作为参数去传递到另一个函数去使用的
区别:之前写的是数据传入函数,而现在介绍的是函数逻辑的传入
add和cut具有具体的实现功能的逻辑代码,但是它没有数据,test_func里我有数据,但是我要你给我一个可传入两个数据的函数,至于你给我的这个函数是要相加还是相减我不管,你给我功能逻辑,我给数据,就实现了整体的功能。
这个写法是比较简单的,主要是清楚这个逻辑,逻辑清楚了,我们就思想就又升华了一下,一定要多自行写写理解
四、lambda匿名函数:
什么是匿名函数:匿名函数就是没有没有函数名的函数,该函数写法简单,生命周期只有一次调用,使用过后该匿名函数就不再存在了。
匿名函数特性:
- 1、匿名函数没有函数名
- 2、除非使用变量去接收匿名函数,否则匿名函数只能使用一次
- 3、匿名函数的函数体只能写在一行
语法:lamdba 形参: 函数体
以上节函数作为参数传递的案例演示
如果你想让该匿名函数可以像def定义的函数一样可以多次调用,那么你就需要用一个参数去接收它
示例如下
# 代码块一
def test_func(func):
result = func(1, 2)
return result
print(test_func(lambda x, y: x + y))
# 代码块二
# 匿名函数的定义
lafunc = lambda x, y: x + y
print(lafunc(3, 2))
print(lafunc(5, 2))
注意事项:
1、匿名函数是使用lambda的关键字去定义的
2、语法:lambda 形参: 函数体(一行代码)
3、匿名函数用于构建临时仅使用一次的场景,如要使用多次则建议使用def定义函数
4、匿名函数的函数体只能能写一行代码,如有多行代码使用def定义函数
5、虽然匿名函数可以通过变量赋值的方式让他多次调用,但从规范建议这类场景使用def方式
五、文件的常用操作:
1、我们代码中例如变量、函数等等数据都是存放在内存中的,当我们程序关闭或者关机之后就会消失
2、如果有一些数据:例如日志或者配置文件等等需要长期保存的数据我们就可以将他存放在文件中,便于数据的管理和检索
3、文章、视频、可执行文件等等都可以保存为一个文件,并赋予一个文件名
4、如果我们要对文件进行操作,那么只要包括打开、关闭、读、写等操作
5.1、文件编码的概念:
计算机是认识0和1,那么我们人类所认识的文本文件内的内容,计算机是如何识别,并存在硬盘中的呢?
在硬盘中所有的数据都是以文件为单位将数据存储在各文件中,而计算机是通过编码技术(密码本)将内容翻译成0和1存储
如下图:【我喜欢你】的中文字符通过编码技术,将其转换为了二进制0和1进行存储在计算机中
反过来,当我们需要查看文件中的内容,同样也是通过编码技术讲存储的二进制内容转换成【我喜欢你】我们人类能看懂的语言
计算机中有许多种编码技术,也就是密码本,我们就可以要求计算机通过某种特定的编码技术将内容翻译成人类能看懂的语言,如果你使用的编码技术不对,那么查看时就会出现我们常见的内容乱码的错误
例如我们电脑中打开一个记事本,我们就可以发现我们电脑使用的是通用的UTF-8的编码技术,还有一些其他的编码规则,如:GBK,Big5等等
5.2、文件读取:
5.2.1、open打开文件并或者一个文件对象:
在python中我们可以使用open函数,可以打开一个已存在的文件或创建一个新文件
语法:open(name,mode,encoding)
- 1、name:是要打开的目标文件名或路径,如你的文件在该代码项目同级则直接就写文件名即可,不在则需给绝对路径
- 2、mode:文件打开的访问模式:只读、写入、追加等等
- 3、encoding:编码格式(推荐通用的UTF-8或你有特定需求的编码格式)
示例如下:
我有一个文本文件在【测试.txt】,它的绝对路径是:E:\测试.txt,其中它的内容如下图,并且该文件用的是UTF-8的编码格式
所以根据open的语法,name为文件的绝对路径,mode填写的是:r(代表只读),encoding:为编码格式UTF-8。
再来回顾一下,我们前面两个实参,其实就是之前说的位置参数,它是对应着形参中的顺序的,但是encoding我们使用了关键字参数,因为encoding它的形参位置不是在第三位,所以我们在这里指定了UTF-8给encoding形参,其他的参数我们没给,但也没异常,那就说明其他参数它是存在缺省参数的。
f = open('E:\测试.txt', 'r', encoding='UTF-8')
print(type(f))
输出结果:最终我们先输出它的类型,最终得到一个名为f的类,该类是对一个text文本文件操作的类,这个类暂时不用纠结是什么,在后续章节会慢慢介绍,现在可以暂时理解为,我的程序得到了该文本文件,我可以对这该文本文件进行相关操作,f就是我们调用这个文件的一个途径
5.2.2、文本内容读取read方法:
我们此刻得到了一个文本文件的类,那么我如何使用这个类,去对我的文本文件进行读取等操作呢?
那我们就可以使用这个文件类的相关方法去实现
语法:文件类.read(num)
num为你要读取出该文本的几个字符,如果不写num则全部字符都会读取出来
# 定义文本类
f = open('E:\测试.txt', 'r', encoding='UTF-8')
# read读取文本内容
print(f.read(10))
输出结果:由于我给read方法填写的num值为10,所以它只读取出了10个字符,但是仔细数一下,加上中途出现的空格只有9个字符呀,哪来的10个,仔细些可以发现,它这个进程结束的这句话是不是比以前间隔多了一行,那是因为我们文本中的\n换行符也算一个字符
那么我想输出文本全部内容,则无需写num,制空即可
# 定义文本类
f = open('E:\测试.txt', 'r', encoding='UTF-8')
print(type(f))
# read读取文本内容
print("=============输出文本10个字符内容=================")
print(f"输出10个字符内容为:{f.read(10)}")
print("=============输出文本全部内容=================")
print(f.read())
输出结果:我们read方法不给num指定参数输出了文本内所有内容,但是有个问题,为什么我输出全部内容的第一行丢失了,没输出出来,而是从内容中的第二行开始输出的,那是因为一个叫做指针的东西影响的
我们代码中使用了两次read,第一个red读取了10个字符的数据,此刻它的指针位移到了第十个字符,当我们第二个read读取所有内容时,它开始找它的指针现在是位于第十个字符处,所有第二个read读取时从第十个它的指针处开始读取
当我把第一个read注释掉以后,就发现我第二个输出全部文本内容的read方法正常了,所以要记住文件的读取是依赖于指针去读取的,当你这个文本类的指针处于哪个位置就从哪个位置开始读取
5.2.3、readlines方法读取文本所有行:
readlines于read功能差不多,都是将文本的内容进行读取,但readlines是将文件以列表的形式进行存储的
语法:文件类.readlines()
作用:读取文本内容所以行数据
示例如下:
# 定义文本类
f = open('E:\测试.txt', 'r', encoding='UTF-8')
print(type(f))
# read读取文本内容
print("=============输出文本10个字符内容=================")
#print(f"输出10个字符内容为:{f.read(10)}")
print("=============输出文本全部内容=================")
print(f.read())
# 读取文件所有行数据
print("=============readlines读取文本全部内容,并封装到列表中=================")
lines = f.readlines()
print(f"它的类型是:{type(lines)}")
print(f"它的内容为:{lines}")
输出结果:此时发现它的类型是一个list列表,没问题,但是它的内容怎么是空的呢?
- 这就是上边说过的指针的影响,我们最后一次读取了全部的文本内容,此刻它的指针是处在内容的末尾,当使用readlines去读取时依然依赖于指针,所以导致它读取的内容为空
- 虽然说我们的read和readlines是两个不同的方法,但它打开的文件使用的都是同一个文本类,这个指针不是存在于方法中的,是存在于文本类中的
当我把影响指针的代码注释掉后,我们的readlines就正常可以将内容封装到列表中了,但是每个列表元素都多了一个\n的符号,这个之前说过\n是换行符,它也转成了字符存于列表中。
注意哦,此刻指针又指向了文本内容的末尾
5.2.4、readline方法单行读取文本内容:
readline和readlines少了一个s,功能上的区别就是,readline一次读取一行数据,存储形式为str字符串,不再是列表类型,再回想下指针,当我再使用一次readline就会读取下一行的数据
语法:文本类.readline()
功能:一次读取一行文本内容,以str字符串类型存储
# 读取文件单行数据
line1 = f.readline()
line2= f.readline()
print(f"line的类型为:{type(line1)}")
print(f"第一次读取line的内容为:{line1}")
print(f"第二次读取line的内容为:{line2}")
输出结果:每执行一次,指针向下移动一行,所以每次执行都是下一行的文本内容,并且它读取的内容是str字符串,没有进行list列表封装
5.2.5、for循环遍历文本类内容:
同时我们也可以使用for循环遍历该文本类的内容
for 临时变量 in 文本类:
# 同时我们也可以使用for循环遍历出文本类的内容
print("=============for循环遍历文本类内容=================")
for line in f:
print(f"每一行数据内容为:{line}")
输出结果:
5.2.6、close关闭文本类对象:
我们为什么要关闭这个文件类对象呢?
如果我们不关闭对象,则程序将会一直占用该文件,例如下图
然后再去对改文件如修改文件名等操作,就会提示目前该文件被占用等情况出现,所以要注意,每次对文件做完操作后,必须关闭这个文件类对象
# 定义文本类
f = open('E:\测试.txt', 'r', encoding='UTF-8')
print(type(f))
# read读取文本内容
print("=============输出文本10个字符内容=================")
#print(f"输出10个字符内容为:{f.read(10)}")
print("=============输出文本全部内容=================")
#print(f.read())
# 读取文件所以行数据
print("=============readlines读取文本全部内容,并封装到列表中=================")
#lines = f.readlines()
# print(f"它的类型是:{type(lines)}")
# print(f"它的内容为:{lines}")
# 读取文件单行数据
line1 = f.readline()
line2= f.readline()
print(f"line的类型为:{type(line1)}")
print(f"第一次读取line的内容为:{line1}")
print(f"第二次读取line的内容为:{line2}")
# 同时我们也可以使用for循环遍历出文本类的内容
print("=============for循环遍历文本类内容=================")
for line in f:
print(f"每一行数据内容为:{line}")
# 关闭文件类对象
f.close()
5.2.7、with open语法:
该语法的作用就是首先open定义一个文件类对象,然后可再该with体内编写对该文本的一系列相关操作,同时它自带.close的关闭文件类对象的方法,不用我们再可以去写关闭,避免因为遗漏等疏忽导致对象未被关闭
语法: with open(name,mode,encoding) as f:
文本操作相关代码
as f就是我们命名的文本类对象名称
print("=============with语法=================")
with open('E:\测试.txt', 'r', encoding='UTF-8') as f:
for line in f:
print(f"每行文本内容为:{line}")
# 这里同样,让它睡眠
time.sleep(50000000)
输出结果:我们使用with可以很好的将本次要处理的一系列操作包含在内。并且它自带了close方法,可以无需再担心喽写关闭,导致文件一直处于占用状态
我们代码中并没有写close关闭方法,同样让他程序睡眠防止关闭,这时候我们再去操作修改文件名称,看看是否还会被占用,with是否自带关闭功能
这里成功将文件进行修改,表示文件已被释放,没被占用
5.2.8、文件读取总结:
1、读取文件需先使用 open(name,mode,encoding) 的函数打开文件获得一个文件对象
2、name为文件的绝对路径,mode为打开后用于的可操作权限,encoding为文件内容读取的编码格式
语法 | 功能说明 |
---|---|
文件对象 = open(name,mode,encoding) | 打开文件并获得一个文件对象 |
文件对象.read(num) | 指定读取字符长度的文件内容,不指定则读取所有内容 |
文件对象.readlines() | 读取文件所有行内容,并以列表保存 |
文件对象.readline() | 读取文件单行内容,并以str字符串保存 |
for 临时变量 in 文件对象: | 一次循环遍历一行文件内容 |
文件对象.close() | 关闭文件对象 |
with open(name,mode,encoding) as 对象名 | 通过with打开文件并获得文件对象,在该with代码体内进行该文件对象的一系列操作,自带close关闭对象功能 |
5.2.9、文件读取案例:
通过windows的文本编辑器,将如下内容复制到word.txt文件中,文件放在哪里都可以
内容为:
coat jacket sweater
shirt swimsuit coat
trousers pants coat
jeans coat coat coat
shoe slippers coat
通过文件读取操作,读取此文件,并统计出现了多少次【coat】
# 定义全局变量,用于计数字符数量
count = 0
# 使用with方式打开文件,并获得file的对象名
with open('E://word.txt', 'r', encoding='UTF-8') as file:
# 使用for循环遍历每行文本的内容,类型为str字符串
for file_str in file:
# 使用字符串的去除首位指定的换行符,
emp_str = file_str.strip('\n')
# 或者使用字符串的replace替换方法也是可以的
# emp_str = file_str.replace('\n', '')
# 每个单词都有空格,所以我们可以使用字符串的分割方法,使其按照空格进行分割成每一个列表元素
emp_list = emp_str.split(' ')
# 得到一个分割好的列表元素后,使用for循环遍历每一个列表元素
for i in emp_list:
# 遍历出来的每个列表元素就用来做比对,是否于我们想统计的内容匹配
if i == 'coat':
# 元素匹配则计数器+1
count += 1
print("统计发现,coat单词在文本中共出现过{count}次")
输出结果:首先我们使用with语法打开文件,然后将文本的内容读取出来(这里读取的方式有很多,不是只有案例中的这一种),本次采用的是for每次循环读取一行文本内容,它的数据类型是str字符串,读取后发现每行数据末尾它都有\n换行符,所以我们可以采用strip(首尾去除指定字符)或者replace(替换字符串中的指定字符)的方式去将换行符给移除掉,移除后每行数据有多个单词,无法直接进行判断匹配,所以我们可以使用字符串的split使其按照每个单词结尾的空格符号进行分割,最后得到一个list列表存储着每个单词元素,这样我们就可以遍历列表,然后对每一个元素进行匹配,匹配上了就count计数器就+1
# 方式二
# 定义全局变量,用于计数字符数量
count = 0
# 使用with方式打开文件,并获得file的对象名
with open('E://word.txt', 'r', encoding='UTF-8') as file:
# 使用read将文本所有内容输出并存放在files变量中,它获得的是一个str字符串数据类型
# 由于该方式并不会将换行符转为符号,所有我们无需在对它的内容做任何处理
files = file.read()
# 使用split分割,默认是按空格,并且不会将换行符转成字符
emp_list = files.split()
# 遍历列表并进行匹配
for i in emp_list:
if i == 'coat':
count += 1
print(f"统计发现,coat单词在文本中共出现过{count}次")
输出结果:
# 方式三——该方法最简单
# 使用with方式打开文件,并获得file的对象名
with open('E://word.txt', 'r', encoding='UTF-8') as file:
# 使用read将文本所有内容输出并存放在files变量中,它获得的是一个str字符串数据类型
files = file.read()
# 直接使用字符串的count,统计出coat的数量
count = files.count('coat')
print(f"统计发现,coat单词在文本中共出现过{count}次")
输出结果:
5.3、文件写入操作:
文件写入:不管是写入还是追加都需要打开文件,open打开文件时就可以对文件对象赋予相对应的权限,写入的权限是:w
语法:文件对象.write(写入内容):当文件不存在时自动创建文件,当文件存在时情况文件内容再写入新内容
注意事项:
1、w只有写入,没有读权限,所有不能使用读的方法
2、由于程序是运行在内存中运行的,它的写入方法也是将内容写入到内存中的缓冲区内,并没有实际写入到硬盘中,所以写完后我们需要使用:文件对象.flush()的方法将缓冲区内的数据写到硬盘,否则你新增的数据并没有实际写到文件中
3、 文件不存在则会自动创建文件,文件存在则会清空原文件内容,再写入新内容
# 打开文件,并获得一个w(写入)权限的文件对象
f = open('E://test.txt', 'w', encoding='UTF-8')
f.write("我叫胡图图")
time.sleep(500000)
输出结果:我这里现在是没有test的这个文件的。
程序运行后,不存在则会自己创建一个文件
并且再写入后,我将此程序睡眠了一段时间,避免程序停止,发现文件中并没有写入内容,这就是开头介绍的,写入只是将内容记录再内存中,我们需要使用.flush()的方法,让他将缓冲区中的数据写入到硬盘才行
# 打开文件,并获得一个w(写入)权限的文件对象
f = open('E://test.txt', 'w', encoding='UTF-8')
f.write("我叫胡图图")
f.flush()
使用flush方法后,内容就写入到文件中了
5.4、文件写入操作:
文件追加:打开文件的权限为:a
追加方法:文件对象.write()
注意事项:文件不存在则创建文件写入内容,文件存在则再文件末尾继续写入新内容,不会情况原内容
# 打开文件,并获得一个a(追加)权限的文件对象
f = open('E://test.txt', 'a', encoding='UTF-8')
f.write("\n我住在翻斗花园二号楼一零零一室")
f.flush()
输出结果:需要换行写入则可以使用\n换行符
5.5、文件操作总结:
mode权限 | 功能说明 |
---|---|
r | 以只读方式打开文件,文件的指针会放在内容的开头 |
w | 文件存在以写入方式打开文件,文件不存在则创建后打开,文件的指针会放在开头,所有文本内原有的内容将会被删除覆盖 |
a | 文件存在以追加写入方式打开文件,文件不存在则创建后打开,文件的指针会放在末尾,所以文本内原有内容不会被删除覆盖,只会在末尾追加新数据 |
b | 以二进制方式打开文件,同时可以于r、w、a并用 |
rb | 以二进制只读方式打开文件 |
wb | 以二进制写入方式打开文件 |
ab | 以二进制追加方式打开文件 |
‘+’ | 具有读写操作权限,通常于a+、w+等等配合使用 |
5.6、文件操作综合案例:
1、读取文件一个叫bill.txt的文件,文件内容如下,分别记录了学生的姓名,入学时间,分数,是否作弊(是:为作弊)
- name,Intake,score,ifcheat
小美,2022-07-01,98,否
王刚,2022-07-02,99,否
二丫,2022-07-05,100,是
图图,2022-07-08,60,否
马小跳,2022-07-01,59,是
小王,2022-07-01,60,否
老王,2022-07-30,96,是
小李,2022-07-21,95,否
老李,2022-07-20,92,是2、 将bill.txt文件中的内容写到bill_bak.txt文件作为有效成绩的备份
3、 文件内作弊的数据行不做备份
# 方式一,该方式遍历出每次外循环得到的整行数据元素
# 虽可以实现该功能,但会消耗较多时间,并且如果改行其他元素存在否,则容易误判
# 已写入方式打开备份文件
bak_file = open('E://bill_bak.txt', 'w', encoding='UTF-8')
# 只读方式打开文件
bill_file = open('E://bill.txt', 'r', encoding='UTF-8')
# 用于记录每次外循环的行,遍历查看,实际功能中可不使用
j = 0
# 文本内容遍历
for file in bill_file:
# 对内容的换行符等特俗字符进行清理
str_file = file.strip()
# 对内容进行分割,按逗号进行分割,便于每个元素的判断
list_file = str_file.split(',')
# 输出当前循环行数的列表内容,遍历查看理解,实际功能中可不写
print(f"第{j}次外循环的列表内容为:{list_file}")
j += 1
# 分割后最终得到一个列表,通过for循环对列表内容进行遍历
for i in list_file:
# 如果本次循环的行,存在是元素,则结束本行循环遍历,让外循环遍历下一行数据
if i == '是':
break
# 如果本次循环的行,存在否元素,则写如外循环遍历的改行数据
elif i == '否':
# 使用write方法将文本循环遍历的改行写入备份文件
bak_file.write(file)
# 刷新磁盘,将内存中的数据写入磁盘
bak_file.flush()
# 既然本行已经找了一个否,写入完成后,则结束本行循环遍历,开始下一轮
break
else:
# 没匹配带是否,就一直循环遍历匹配本行所有元素位置
continue
# 关闭文件
bak_file.close()
bill_file.close()
输出结果:最终整理出,如下图有效成绩学生的信息
# 方式二,发现内容是有规律的,分割为列表后,我们需判断的元素都在下标3上,那么我们取列表元素进行比较时
# 就可以只判断元素下标为3的数据是否为否,即可
# 已写入方式打开备份文件
bak_file = open('E://bill_bak.txt', 'w', encoding='UTF-8')
# 只读方式打开文件
bill_file = open('E://bill.txt', 'r', encoding='UTF-8')
for file in bill_file:
# 去除换行等特俗字符
str_file = file.strip('\n')
# 按逗号对改行元素进行分割,得到一个列表,并且要匹配的数据,下标都为3,所有,仅需将下标为3的元素取出即可
list_file = str_file.split(',')[3]
# 判断下标为3的元素,是否为:是
if list_file == '是':
# 为:是,则直接跳过本次循环,本次只有一个循环,所有不能用break,不然行数据没遍历匹配完,就结束了
continue
else:
# 写入数据,并刷新磁盘
bak_file.write(file)
bak_file.flush()
# 关闭文件
bak_file.close()
bill_file.close()
输出结果:方式二的话就优化了方式一的匹配效率和完成度
六、异常的常用操作:
6.1、什么是异常:
异常也就是我们常说的BUG,当Python解释器检测出程序中的某段代码无法正常执行时,提示的相关错误信息就称之为异常(BUG)
例如:上节了解的open函数,如果采用r只读方式去读取一个不存在的文件,则会提示如下图所示的异常信息
上图,我们使用了open函数找读取一个不存在的文件,提示了第4行的****代码块,解释器报出没有查找到文件的异常,找不到的文件是:E://abc.txt
6.2、为什么要捕获异常:
没有完美的代码,任何程序在运行中,都有可能出现异常,导致程序无法完美的运行下去。
我们能做的不是追求完美的代码,而是针对可能出现的异常,提前预知、准备及处理,这种行为称之为:异常捕获
当程序出现异常通常会出现以下两种情况
- 1、因为某个异常,导致整个程序停止运行
- 2、对异常进行提醒,并让程序继续运行
在之前我们所编写的代码中,出现了异常,我们都是出现了第一种方式,当遇到异常,整个程序就停止了运行。
而我们不能因为一个小异常就停止整个程序,我们更希望的是第二种方式,让异常提醒,保证程序继续运行,这时就需要用到异常捕获。
异常捕获的作用:提前假设某处会出现异常,提前做好准备,让异常出现时可以有后续的手段去避免异常
其实在第二章时我们就对用户输入是否符合规范时使用到了异常
当时需用户输入数字,但为防止用户胡乱输入,写了一个def的函数,函数体内的try代码块就是捕获了,str这个变量转换为float浮点数是否正常,当出现异常,则返回了False,表示用户输入的非数字类型,正确则返回True,表示用户输入符合规范
def is_decimal_number(str):
try:
float(str)
return True
except ValueError:
return False
6.3、捕获常规异常:
语法:try:
- 可能出现异常的代码块
except:
- 异常出现时执行的代码块
例如之前的代码,当我们open只读打开一个不存在的问题,当只要提示出异常,则执行except代码块,将权限r改为w,避免异常导致程序中断
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
except:
f = open('E://abc.txt', 'w', encoding='UTF-8')
上述代码中,我们没有执行异常类型,通用都去执行那一个代码块,这时编码工具会提示我们,该异常语句过于宽泛,也就是说这个对应了太多的异常情况,不一定能准确的处理该异常
6.3、捕获指定异常:
程序它可能会出现不同的异常,不同的异常可能需要用不同的代码去补救,这时我们就需要指定哪些异常去执行哪些代码块的操作了
语法:try:
- 可能出现异常的代码块
except 异常类型 as 自定义异常对象名称:
- 异常出现时执行的代码块
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
except FileNotFoundError as not_file_erro:
print("出现异常了,打开的文件不存在,将使用w模式打开,自动创建文件")
f = open('E://abc.txt', 'w', encoding='UTF-8')
输出结果: 这时我们可以给它指定异常处理的代码块,针对不同异常去执行对应的解决代码块方案
下方指定了找不到文件的异常,去使用如下代码去解决,并且文件不存在也不再会提示异常导致程序中断,而是将打开文件的模式改为w,文件不存在时w会自动创建文件
那么异常有很多,上述只是捕获了没有文件的异常,那么别的异常信息它能捕获吗?
此处在try可能出现异常的代码块中加了一个打印name变量内容是代码,我们的name是没有被定义的
那么这个代码肯定会出现异常
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
print(name)
except FileNotFoundError as not_file_erro:
print("出现异常了,打开的文件不存在,将使用w模式打开,自动创建文件")
f = open('E://abc.txt', 'w', encoding='UTF-8')
执行后发现我们指定捕获了文件不存在的异常类型,但是print(name)是另外一个异常类型,所有变量未定义的异常没有被捕获,异常还是抛出,导致程序异常中断了
6.4、捕获多个异常:
针对上述的问题,我们可以通过指定多个异常来进行相应的补救代码
语法:try:
- 可能出现异常的代码块
except 指定异常类型一 as 自定义异常对象名称:
- 异常出现时执行的代码块
except 指定异常类型二 as 自定义异常对象名称:
- 异常出现时执行的代码块
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
print(name)
except FileNotFoundError as not_file_erro:
print("出现异常了,打开的文件不存在,将使用w模式打开,自动创建文件")
f = open('E://abc.txt', 'w', encoding='UTF-8')
except NameError as a:
name = 'haha'
print(f"name变量未被定义,异常处理后,定义了该变量,该变量的初始值为:{name}")
输出结果:这样就可以实现多个指定异常,分别不同的处理代码块
同时,我们也可以通过as定义的异常对象名称(别名)去打印出具体的异常信息
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
print(name)
except FileNotFoundError as not_file_erro:
print("出现异常了,打开的文件不存在,将使用w模式打开,自动创建文件")
f = open('E://abc.txt', 'w', encoding='UTF-8')
except NameError as a:
name = 'haha'
print(f"name变量未被定义,异常处理后,定义了该变量,该变量的初始值为:{name}")
print(f"异常信息为:{a}")
输出结果:可以看到我们打印了a这个异常对象,它的内容是name这个变量没有被定义
6.5、多个异常通用代码块:
如果可能遇到多个异常,但是他们的补救代码块是一样的,那么我们可以使用如下语法去精简代码量
语法:try:
- 可能出现异常的代码块
except (指定异常类型一,指定异常类型二) as 自定义异常对象名称:
- 异常出现时执行的代码块
except 指定异常类型三 as 自定义异常对象名称:
- 异常出现时执行的代码块
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
num = 1 / 0
print(name)
except FileNotFoundError as not_file_erro:
print("出现异常了,打开的文件不存在,将使用w模式打开,自动创建文件")
f = open('E://abc.txt', 'w', encoding='UTF-8')
except (ZeroDivisionError, NameError) as a:
num = 1/1
print("可能出现了变量未定义 或 0被作为除数的异常")
name = '哈哈'
print(f"异常信息为:{a}")
print(f"目前已将除法改为默认1/1,name默认值为{name}")
输出结果:我们知道0是不能被当作除数的,否则会提示【ZeroDivisionError】的异常类型,我们将【ZeroDivisionError】和【NameError】这两个可能出现的异常都写在同一补救代码块中一起处理
6.6、捕获所有异常:
我们不确定会出现哪些异常,我们需要它都给捕获下来,避免程序中断
语法:try:
- 可能出现异常的代码块
except Exception as all_erro: (也可以不写Exception,默认就是Exception)
- 异常出现时执行的代码块
try:
print(name)
1 / 0
except Exception as all_erro:
print(f"出现异常了,异常信息为:{all_erro}")
最终我们就可以捕获到所有的异常,Exception其实是顶级的异常,之前遇到的文件不存在、变量未定义这些其实都是Exception的小弟
6.7、捕获异常的else:
上面是介绍了有异常就会做有异常的事情,那么没异常也可以让程序去做没异常的事情,就可以使用else实现
语法:try:
- 可能出现异常的代码块
except Exception as all_erro: (也可以不写Exception,默认就是Exception)
- 异常出现时执行的代码块
else:
- 没有异常时执行的代码块
try:
print("这是一段正常的代码")
except Exception as all_erro:
print(f"系统出现了异常,异常信息为:{all_erro}")
else:
print("程序正常执行")
输出结果:代码中输出了一段话,这个语句肯定是没异常的,最终else表示当程序未出现异常时,则执行的代码块
6.8、捕获异常的finally:
当程序有异常回去执行相应异常类的代码块,没异常也可以去执行没异常的代码块,同时也有程序不管有没有异常都会去执行的代码块,就可以使用finally实现
语法:try:
- 可能出现异常的代码块
except Exception as all_erro: (也可以不写Exception,默认就是Exception)
- 异常出现时执行的代码块
else:
- 没有异常时执行的代码块
finally:- 不管有没有异常都会执行的代码块
try:
f = open('E://abc.txt', 'r', encoding='UTF-8')
except Exception as all_erro:
f = open('E://abc.txt', 'w', encoding='UTF-8')
print(f"系统出现了异常,异常信息为:{all_erro}")
else:
print("程序正常执行")
finally:
print("不管有没有异常都会执行,关闭文件对象")
f.close()
输出结果:我们只读打开一个不存在的文件,检测出异常,然后执行了异常里的代码块将权限改为w,自动创建了不存在的文件,最后执行了finally代码块,不管异常与否,都会去进行的操作,通常用于关闭资源
6.9、捕获异常总结:
关键字 | 作用说明 |
---|---|
try | 写入可能出现异常的代码块 |
except | 捕获所有异常,并给出提示防止程序中断,或直接执行相应异常处理的代码块 |
except Exception as 异常对象别名: | 捕获所有异常,并给出提示防止程序中断,或直接执行相应异常处理的代码块 |
except (异常类型一、异常类型二) as 异常对象别名: | 已定义元组的方式,填写多个异常,用于捕获多个异常 |
else: | 当未出现异常时需要执行的代码块 |
finally: | 不管是否出现异常,都百分百执行的代码块 |
7、异常的传递:
例如以下代码:有一个func_1的函数写了一段1 / 0的代码,这肯定会异常,然后func_2调用了func_1,main函数又调用了func_2,最终我们调用main函数运行,发现func_1中出现的异常会依次传递到最高一级的main函数中
如果我们要对这个异常进行捕获,是需要在问题根源的函数上去捕获,还是在最高一级的main函数中捕获呢?
# 定义一个肯定出现异常的函数func1
def func_1():
print("func_1开始执行了")
num = 1 / 0
print("func_1结束执行了")
# 定义func2,调用func1出现异常的函数
def func_2():
print("func_2开始执行了")
func_1()
print("func_2结束执行了")
# 定义main的主函数,调用func2
def main():
print("main开始执行了")
func_2()
print("main结束执行了")
# 调用main主函数
main()
执行结果:
从上图我们看出异常是具有传递性的,最终传递到了main函数,遇到类似问题,我们可以直接在最高一级的main函数中去捕获异常即可,无需到底层函数去捕获,到底层去捕获,当出现异常时反而排查问题变得更困难,跟套娃似的
# 定义一个肯定出现异常的函数func1
def func_1():
print("func_1开始执行了")
num = 1 / 0
print("func_1结束执行了")
# 定义func2,调用func1出现异常的函数
def func_2():
print("func_2开始执行了")
func_1()
print("func_2结束执行了")
# 定义main的主函数,调用func2
def main():
print("main开始执行了")
try:
func_2()
except Exception as erro:
print(f"程序出现异常了,异常信息为:{erro}")
print("main结束执行了")
# 调用main主函数
main()
输出结果:
异常传递流程图