首页 > 其他分享 >16.异常处理

16.异常处理

时间:2023-04-08 20:55:15浏览次数:37  
标签:文件 name 16 处理 open try print path 异常

异常处理

什么是异常

本节开始介绍之前,先看看如下程序:

>>> print(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

是不是很熟悉,这是我们前面经常看到的程序运行出现的错误。

作为Python初学者,在学习Python编程的过程中,经常会看到一些报错信息,使你编写的程序不能如期工作,如我们前面看到过的NameErrorSyntaxError、TypeErrorValueError等,这些都是异常。

异常是一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生异常。异常是Python的对象,表示一个错误。当Python脚本发生异常时,我们需要捕获并处理异常,否则程序会终止执行。

每一个异常都是一些类的实例,这些实例可以被引用,并且可以用很多种方法进行捕捉,使得错误可以被处理,而不是让整个程序失败。

异常处理

出现异常怎么办呢?

就如我们使用的工具出了点小毛病,我们可以想办法修理好它。程序也一样,前辈们经过不断积累与思考,创造了不少好方法处理程序中的异常,最简单的是使用try语句处理。

try语句的基本形式为try / excepttry / except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。如果你不想在发生异常时结束程序,只需在try语句块中捕获异常即可。

捕获异常的语法如下:

try:
    <语句>  #运行别的代码
except <名字>:
    <语句>  #如果在try部分引发了异常

try的工作原理是,开始一个try语句后,Python就在当前程序的上下文中做标记,当出现异常时就可以回到做标记的地方。首先执行try子句,接下来发生什么依赖于执行时是否出现异常。

如果try后的语句执行时发生异常,程序就跳回try并执行except子句。异常处理完毕后,控制流就可以通过整个try语句了(除非在处理异常时又引发新异常)。

例如以下示例所示(exp_exception.py):

def exp_exception(x, y):
    try:
        result = x / y
        print('计算结果: ', result)
    except:
        print('程序出错: 除数不能为零')

程序执行结果如下:

程序出错: 除数不能为零

由执行结果看到,程序最后执行的是except子句,如果语句正常,应该打印name变量的值。

抛出异常

Python使用raise语句抛出一个指定异常。我们可以使用类(Exception的子类)或实例参数调用raise语句引发异常。使用类时程序会自动创建实例。

例如:

>>> raise Exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception
>>> raise NameError('This is NameError')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: This is NameError
>>> 

由操作结果看到,第一个示例raise Exception引发了一个没有相关错误信息的普通异常,第二个示例输出了一些错误提示。

如果只想知道是否抛出了异常,并不想处理,使用一个简单的raise语句就可以再次把异常抛出,例如:

try:
    raise NameError('This is NameError')
except NameError:
    print('An exception happened')
    # raise 不加reise 输出对应字符结束程序
    
try:
    raise NameError('This is NameError')  # 当前语句中的信息被输出
except NameError:
    print('An exception happened')
    # 添加raise则打印对应字符并再次显示异常
    raise

由输出结果看到,使用raise可以输出更深层次的异常。在使用过程中,可以借助该方法得到更详尽的异常信息。

异常中的else

如果程序执行完异常还需要做其他事情,怎么办呢?

异常为我们提供了try…except…else语句实现该功能,语法如下:

try:
    <语句>  # 运行别的代码
except <名字>:
    <语句>  # 如果在try部分引发了异常
else:
    <语句>  # 如果没有发生异常

如果在try子句执行时没有发生异常,就会执行else语句后的语句(如果有else)。使用else子句比把所有语句都放在try子句里面更好,这样可以避免一些意想不到而except又没有捕获的异常。

例如:

def model_exception(x, y):
    try:
        a = x / y
    except:
        print('程序出现异常...')
    else:
        print('程序无异常则执行此语句...')


model_exception(2, 1)

执行结果如下:

程序无异常则执行此语句...

由执行结果看到,没有发生异常时,会执行else子句的流程。

综上所述,当程序没有发生异常时,通过添加一个else子句做一些事情(比如输出一些信息)很有用,可以帮助我们更好地判断程序的执行情况。

自定义异常

尽管内建异常类包括大部分异常,而且可满足很多要求,但有时还是要创建自己的异常类。比如需要精确知道问题的根源,就需要使用自定义异常精确定位问题。可以通过创建一个新exception类拥有自己的异常。异常应该继承自Exception类,可以直接继承,也可以间接继承。

因为错误就是类,捕获一个错误就是捕获该类的一个实例,因此错误并不是凭空产生的,而是由一些不合理的部分导致的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。如果要抛出错误,那么可以根据需要定义一个错误的类,选择好继承关系,然后用raise语句抛出一个错误的实例。

例如(my_error.py):

class MyError(Exception):
    def __init__(self):
        pass

    def __str__(self):
        return 'this is self define error'


def my_error_test():
    try:
        raise MyError()
    except MyError as e:
        print('exception info: ', e)


my_error_test()

执行结果如下:

exception info:  this is self define error

由程序和执行结果看到,程序正确执行了自定义的异常,并且需要继承Exception类。

这只是一个简单的示例,还有不少细节需要琢磨,此处不做深入探讨,有兴趣的同学可以查阅相关资料进行实践。

提示:异常最好以Error结尾,一方面贴近标准异常的命名,另一方面便于见名知意。

finally 子句

Python中的finally子句需要和try子句一起使用,组成try / finally的语句形式,try / finally语句无论发生异常与否都将执行最后的代码。

例如(use_finally.py):

def use_finally(x, y):
    try:
        a = x / y
    finally:
        print('No matter what happened, I will show in front of you')


use_finally(2, 0)

执行结果为:

Traceback (most recent call last):
  File "/Users/poppies/Desktop/python_projects/基础部分/use_finally.py", line 8, in <module>
    use_finally(2, 0)
  File "/Users/poppies/Desktop/python_projects/基础部分/use_finally.py", line 3, in use_finally
    a = x / y
ZeroDivisionError: division by zero
No matter what happened, I will show in front of you

由执行结果看到,finally子句被执行了,无论try子句中是否发生异常,finally都会被执行。

这里我们有一个疑问,虽然执行了finally子句,但是最后还是抛出异常了,是否可以使用except截获异常呢?

可以使用except截获异常。try、except、elsefinally可以组合使用,但要记得elseexcept之后,finallyexceptelse之后。

对于上面的示例,可以更改如下(use_finally_1.py):

def use_finally(x, y):
    try:
        a = x / y
    except ZeroDivisionError:
        print('Some bad thing happened: division by zero')
    finally:
        print('No matter what happened, I will show in front of you')


use_finally(2, 0)

执行结果如下:

Some bad thing happened: division by zero
No matter what happened, I will show in front of you

由执行结果看到,先执行了except子句的输出语句,后面跟着执行了finally子句的输出语句。如果再添加else子句,当程序正常运行时会先执行else子句,然后执行finally子句。在有finally的异常处理程序中,finally中的子句一定是最后执行的。finally子句在关闭文件或数据库连接时非常有用(文件操作和数据库操作后面会具体讲解)。

提示:Python 2.5之前的版本中,finally需要独立使用,不能与try语句配合。在Python 2.5之后才支持这些语句的组合使用。

文件操作

打开文件

Python中,打开文件使用的是open函数。open函数的基本语法如下:

open(file_name [, access_mode][, buffering])

参数解析:

  • file_name变量:是一个包含要访问的文件名称的字符串值。

  • access_mode变量:指打开文件的模式,对应有只读、写入、追加等。access_mode变量值不是必需的(不带access_mode变量时,要求file_name存在,否则报异常),默认的文件访问模式为只读(r)

  • buffering:如果buffering的值被设为0,就不会有寄存;如果buffering的值取1,访问文件时就会寄存行;如果将buffering的值设为大于1的整数,表示这就是寄存区的缓冲大小;如果取负值,寄存区的缓冲大小就是系统默认的值。

open函数返回一个File(文件)对象。File对象代表计算机中的一个文件,是Python中另一种类型的值,就像我们熟悉的列表和字典。

例如(file_open_1.py):

path = 'd:/test.txt'
f_name = open(path)
print(f_name.name)

执行结果如下:

d:/test.txt

执行结果告诉我们打开的是d盘下的test.txt文件(执行该程序前,已经创建了一个名为test.txt的文件)。

这里有几个概念要先弄清楚:

  • 文件路径:在该程序中,我们先定义了一个path变量,变量值是一个文件的路径。文件的路径是指文件在计算机上的位置,如该程序中的d:/test.txt是指文件在d盘、文件名为test.txt。文件路径又分为绝对路径和相对路径。

    • 绝对路径:总是从根文件夹开始。比如在Windows环境下,一般从c盘、d盘等开始,c盘、d盘被称为根文件夹,在该盘中的文件都得从根文件夹开始往下一级一级查找。在Linux环境下,一般从usr、home等根文件开始。比如在上面的示例程序中,path变量值就是一个绝对路径,在文件搜索框中输入绝对路径可以直接找到该文件。

    • 相对路径:相对于程序当前工作目录的路径。比如当前工作文件存放的绝对路径是d:\python\workspace,如果使用相对路径,就可以不写这个路径,用一个“.”号代替这个路径值。

例如(file_open_2.py):

path = './test.txt'
f_name = open(path, 'w')
print(f_name.name)

执行结果如下:

./test.txt

除了单个点(.),还可以使用两个点(..)表示父文件夹(或上一级文件夹)。此处不具体讨论,有兴趣可以自己尝试。

文件模式

我们在前面讲到,使用open函数时可以选择是否传入mode参数。在前面的示例中,mode传入了一个值为w的参数,这个参数是什么意思呢?mode可以传入哪些值呢?

mode常用的模式:

  • r:表示文件只能读取
  • w:表示文件只能写入
  • a:表示打开文件,在原有内容的基础上追加内容,在末尾写入
  • w+:表示可以对文件进行读写双重操作

mode二进制常用模式:

  • rb:以二进制格式打开一个文件,用于只读
  • wb:以二进制格式打开一个文件,用于只写
  • ab:以二进制格式打开一个文件,用于追加
  • wb+:以二进制格式打开一个文件,用于读写

使用open函数时,明确指定读模式和什么模式都不指定的效果是一样的,我们在前面的示例中已经验证。

使用写模式可以向文件写入内容。+参数可以用到其他任何模式中,指明读和写都是允许的。比如w+可以在打开一个文件时用于文件的读写。

当参数带上字母b时,表示可以用来读取一个二进制文件。Python在一般情况下处理的都是文本文件,有时也不能避免处理其他格式的文件。

基本文件方法
读和写

open函数返回的是一个File对象,有了File对象,就可以开始读取内容。如果希望将整个文件的内容读取为一个字符串值,可以使用File对象read()方法。

read()方法从一个打开的文件中读取字符串。需要注意,Python字符串可以是二进制数据,而不仅仅是文字。

语法如下:

fileObject.read([count])

fileObjectopen函数返回的File对象count参数是从已打开的文件中读取的字节计数。该方法从文件的开头开始读入,如果没有传入count,就会尝试尽可能多地读取内容,很可能一直读取到文件末尾。

比如我们在test.txt文件中写入Hello world!Welcome!,执行如下代码(file_read.py):

path = './test.txt'
f_name = open(path, 'r')
print(f'read result: {f_name.read(12)}')

执行结果如下:

read result: Hello World!

print('read result:', f_name.read(12)) 更改为 print('read result:',f_name.read()),得到的执行结果如下:

read result: Hello world!Welcome!

由执行结果看到,没有指定读取字节数时,read方法会读取打开文件中的所有字节。

除了读取数据外,我们还可以向文件中写入数据。在Python中,将内容写入文件的方式与print函数将字符串输出到屏幕上类似。

如果打开文件时使用读模式,就不能写入文件,即不能用下面这种形式操作文件:

open(path, 'rw')

Python中,用write()方法向一个文件写入数据。write()方法可将任何字符串写入一个打开的文件。需要注意,Python字符串可以是二进制数据,而不仅仅是文字。

write()方法不会在字符串结尾添加换行符('\n'),语法如下:

fileObject.write(string)

fileObjectopen函数返回的File对象string参数是需要写入文件中的内容。

该方法返回写入文件的字符串的长度。

例如(file_write.py):

f_name = open(path, 'w')
print(f"write length: {f_name.write('Hello World!')}")

执行结果如下:

write length: 12

由执行结果看到,我们向test.txt文件中写入了12个字符。下面验证一下写入的是否是我们指定的字符,在上面的程序中追加两行代码并执行:

f_name = open(path, 'r')
print('read result: ', f_name.read())

执行结果如下:

read result: Hello World!

由执行结果看到,写入文件的是我们指定的内容。不过这里有一个疑问,我们在这里执行了两次写入操作,得到的结果怎么只写入了一次?

写文件write方法的处理方式是:将覆写原有文件,从头开始,每次写入都会覆盖前面所有内容,就像用一个新值覆盖一个变量的值。若需要在当前文件的字符串后追加字符,该怎么办呢?

可以将第二个参数w更换为a,即以追加模式打开文件,例如(file_add.py):

path = './test.txt'
f_name = open(path, 'w')
print(f"write length: {f_name.write('Hello World!')}")
f_name = open(path, 'r')
print(f'read result: {f_name.read()}')

# 内容追加写入
f_name = open(path, 'a')
print(f"add length: {f_name.write('welcome!')}")
f_name = open(path, 'r')
print(f'read result: {f_name.read()}')

执行结果如下:

write length: 12
read result: Hello World!
add length: 8
read result: Hello World!welcome!

由执行结果看到,输出结果在文件末尾成功添加了对应字符串。

提示:如果传递给open函数的文件名不存在,写模式w和追加模式a就会创建一个新的空文件,然后执行写入或追加。

如果想追加的字符串在下一行,该怎么办呢?

Python中,用\n表示换行。对于上面的示例,若需要追加的内容在下一行,可以如下操作(file_change_line.py):

path = './test.txt'
f_name = open(path, 'w')
print(f"write length: {f_name.write('Hello World!')}")
f_name = open(path, 'r')
print(f'read result: {f_name.read()}')

f_name = open(path, 'a')
print('add length: ', f_name.write('\nwelcome!'))
f_name = open(path, 'r')
print(f'read result: {f_name.read()}')

执行结果如下:

write length: 12
read result: Hello World!
add length:  9
read result: Hello World!
welcome!

由执行结果看到,后面追加的内容在下一行了。

提示:若需要读或写特定编码方式的文本,则需要给open函数传入encoding参数;若需要读取GBK编码的文件,则前面的示例可以改写为f_name=open(path, 'r',encoding='gbk'),这样读取到的文件就是GBK编码方式的文件了。

读写行

我们目前对文件的读操作是按字节读或整个读取,而写操作是全部覆写或追加,这样的操作在实际应用中很不实用。

Python为我们提供了readline()readlines()writelines()等方法用于行操作,例如(file_read_write.py):

path = './test.txt'
f_name = open(path, 'w')
f_name.write('Hello World!\n')
f_name = open(path, 'a')
f_name.write('welcome!')
f_name = open(path, 'r')
print(f'readline result: {f_name.readline()}')

执行结果为:

readline result: Hello World!

由执行结果得知,readline方法会从文件中读取单独一行,换行符为\nreadline方法如果返回一个空字符串,说明已经读取到最后一行了。

readline方法也可以像read方法一样传入数值读取对应的字符数,传入小于0的数值表示整行都输出。

如果将上面示例的最后一行:

print(f'readline result: {f_name.readline()}')

更改为:

# 当前读取方式为: readlines
print(f'readlines result: {f_name.readlines()}')

得到的输出结果为:

readlines result: ['Hello World!\n', 'welcome!']

输出结果为一个字符串的列表。列表中的每个字符串就是文本中的每一行,并且换行符也会被输出。

readlines方法可以传入数值参数,当传入的数值小于等于列表中一个字符串的长度值时,该字符串会被读取;当传入小于等于0的数值时,所有字符都会被读取。

例如(file_read_lines.py):

path = './test.txt'
f_name = open(path, 'w')
str_list = ['Hello World!\n', 'welcome!\n', 'welcome!\n']
# 当前代码使用writelines进行数据写入
f_name.writelines(str_list)
f_name = open(path, 'r')
print(f'read result: {f_name.read()}')
f_name = open(path, 'r')
print(f'readline result: {f_name.readline()}')

执行结果如下:

read result: Hello World!
welcome!
welcome!

readline result: Hello World!

由执行结果看到,writelines方法和readlines方法相反,传给它一个字符串列表(任何序列或可迭代对象),它会把所有字符串写入文件。如果没有writeline方法,那么可以使用write方法代替这个方法的功能。

关闭文件

我们前面介绍了很多读取和写入文件的内容,都没有提到在读或写文件的过程中出现异常时该怎么处理。在读或写文件的过程中,出现异常的概率还是挺高的,特别对于大文件的读取和写入,出现异常更是家常便饭。在读或写文件的过程中,出现异常该怎么处理呢?

这就需要用到前面介绍的异常的知识了,用try语句捕获可能出现的异常。在捕获异常前有一个动作要执行,就是使用close方法关闭文件。

一般情况下,一个文件对象在退出程序后会自动关闭,但是为了安全起见,还是要显式地写一个close方法关闭文件。

一般显式关闭文件读或写的操作如下(file_close.py):

path = './test.txt'
f_name = open(path, 'w')
print(f"write length: {f_name.write('Hello World!')}")
f.name.close()

这段代码和没有加close方法的执行结果一样。这样处理后的函数比没有加close方法时更安全,可以避免在某些操作系统或设置中进行无用的修改,也可以避免用完系统中所打开文件的配额。

对内容更改过的文件一定要记得关闭,因为写入的数据可能被缓存,如果程序或系统因为某些原因而崩溃,被缓存部分的数据就不会写入文件了。为了安全起见,在使用完文件后一定要记得关闭。

当使用try语句出现异常时,即使使用了close方法,也可能不被执行,这时该怎么办呢?

还记得finally子句吗?可以将close方法放在finally子句中执行,从而保证无论程序是否正常执行都会调用close方法。

上面的示例可以更改成更安全的形式(file_safe_close.py):

f_name = None
path = './test.txt'
try:
    f_name = open(path, 'w')
    print(f"write length: {f_name.write('Hello World!')}")
except Exception as e:
    print(f'程序异常: {e}')
finally:
    if f_name:
        print(f_name.name)
        f_name.close()

如果每次都要这么写,就会很烦琐,是否有更简便的方式处理呢?

Python中引入了with语句自动帮我们调用close方法。可以使用with语句将上面的程序更改为(file_safer_close.py):

path = './test.txt'
with open(path, 'w') as f:
    print(f"write length: {f.write('Hello World!')}")


with open(path, 'r') as f:
    print(f'文件内容: {f.read()}')

这段代码和上面使用try/finally的效果一样,并且会自动调用close方法,不用显式地写该方法。可以发现,代码比前面简洁多了,后面可以多用这种方式编写。

标签:文件,name,16,处理,open,try,print,path,异常
From: https://www.cnblogs.com/irponies-python/p/17299212.html

相关文章

  • java -- 异常处理、Collection、Iterator迭代器、泛型
    异常处理Java异常处理的五个关键字:try、catch、finally、throw、throws抛出异常throw在编写程序时,我们必须要考虑程序出现问题的情况当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的......
  • 1653. 使字符串平衡的最少删除次数
    题目链接:1653.使字符串平衡的最少删除次数方法:动态规划解题思路对于字符串\(s\),设使得字符串\(s[0,i]\)平衡的最小删除次数为\(dp[i]\)。若\(s[0,n-2]\)为平衡字符串,当\(s[n-1]==b\)时,则\(dp[n-1]=dp[n-2]\);当\(s[n-1]==a\)时,则\(dp[n-1]=min(dp[n-2]+1\),\(a\)......
  • Laravel日期处理
    1.常用:echoCarbon::now();//2016-10-1415:18:34echoCarbon::today();//2016-10-1400:00:00echoCarbon::tomorrow('Europe/London');//2016-10-1400:00:00echoCarbon::yesterday();......
  • 『0016』 - Solidity Types - 玩转 Solidity 数组 (Arrays)
    作者:黎跃春,学习目标掌握Arrays的可变不可变的创建深度理解可变数组和不可变数组之间的区别二维数组memoryarrays的创建bytes0~bytes32、bytes与byte[]对比固定长度的数组(Arrays)固定长度类型数组的声明pragmasolidity^0.4.4;contractC{//数组的长度为5,数组里面的存储......
  • 基于matlab模拟雷达信号检测中的恒虚警处理方法(慢门限和快门限)
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • MQ——消息积压如何处理
    一、消息积压的原因1、producer生产消息突然增多比如大促期间,抢购,秒杀业务。2、consumer消费性能突然下降比如消费失败时重试,程序死锁,io阻塞。3、消费系统本身出现瓶颈这种情况很少,对于绝大多数使用消息队列的业务来说,消息队列本身的处理能力要远大于业务系统的处理能力。主......
  • 常见的OOM异常
    Java.lang.StackOverFlowError虚拟机栈溢出java.lang.OutOfMemoryError:javaheapspace堆区内存溢出java.lang.OutOfMemoryError:GCoverheadlimitexceeded大量资源都用于GC回收了,每次GC后也清理不了多少空间,反复如此,就抛出此错误java.lang.OutOfMemoryErro......
  • python之json格式化与紧凑处理
    格式化在工作中json是我们常用的数据格式,因为格式化与紧凑存储所占的内存是不同的,格式化存储接近大一倍空间。所以有时候需要紧凑存储(一行存储),但是查看不太方便。场景:记事本打开json的速度最快,但是没有格式化功能。notepad++可以格式化但是需要联网安装插件,内网环境不能下载......
  • 冷知识:预处理字符串操作符
    以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/Xr2pFCJ4j0DZYo2PO6-KQg当年学习C语言的第一门课就提到过标记(Token)的概念,不过,相信在多年之后你再次听到这个术语时会一脸懵逼,比如我。因此特地翻了翻资料,整理下来这些笔记......
  • C#.NET 国密 BASE64编码的私钥提取16进制私钥
    C#.NET国密BASE64编码的私钥提取16进制私钥,从BASE64编码的公钥中提取16进制字符串公钥, 从BASE64编码的私钥中提取16进制字符串私钥, 锦州银行在使用这种私钥。 StringmchtPubKey="MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAElmWpvTHHsQEUMSLoMcDssXAjCkdgjCkncPXNnnapIEk......