Python - 异常处理

995 阅读7分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

微信公众号搜索【程序媛小庄】,关注半路出家的程序媛如何靠python开发养家糊口~

前言

运行中的程序不知道怎么搞的报错了,恰好没有处理这个报错,程序也随之终止了,有没有什么办法能够让程序报错也能够不崩掉继续运行呢?答案是有的,就是通过异常处理。

什么是异常

异常顾名思义就是不正常,是程序发出错误的信号,程序一旦出现错误,就会产生异常,如果没有处理该异常的话,程序就会随之终止。比如以下程序抛出异常:

>>> x
Traceback (most recent call last):  # Traceback:追踪回溯异常
  File "<stdin>", line 1, in <module>  # 定位异常的位置
NameError: name 'x' is not defined  # NameError:异常的类型

异常大致分为两种情况:

一种是语法错误SyntaxError,语法错误在程序运行前就应该修改,属于低级错误~

>>> if
  File "<stdin>", line 1
    if
      ^
SyntaxError: invalid syntax

另一种是逻辑上的错误,比如TypeError、NameError、IdenxError

# TypeError:数字类型与字符串类型不能进行计算
1 + ’2# ValueError:类型转换的错误
num = input(">>: ")  # 比如输入的是'python'
int(num)

# NameError:引用了一个不存在的名字x
x

# IndexError:索引超出列表的限制
list1 = ['python','java']
l[3]

# KeyError:引用了一个不存在的key
dic={'name':'python'}
dic['age']

# AttributeError:对象属性不存在
class Test:
    pass
Foo.x

如何进行异常处理

为了增强程序的健壮性,即便是程序运行过程中出错了,也不要终止程序。而是捕捉异常并处理:将出错信息记录到日志内。

语法错误

语法上出现的错误必须在程序运行前更正。

if True  #  SyntaxError: invalid syntax
	print('if分支没有加:')

逻辑上的错误

逻辑上的错误分为两种情况,一种是错误的条件可以预知,另一种就是发生错误的条件无法预知。

可以预知发生错的误条件

如果错误的条件可以预知,可以通过if判断解决,比如:

age = 18
inp_age = input('>>').strip()
# 可以预知字符串与数字无法进行大小的比较
if inp_age.isdigit():
    inp_age = int(inp_age)
    if age > inp_age:
        print('bigger')
    else:
        print('error')
else:
    print('请输入数字')

无法预知发生错误的条件

在无法预知发生错误的条件的情况下,保证程序的可靠性,使程序不会崩溃终止,就需要对异常进行处理,异常处理的基本形式如下:

try:
    需要检查是否会出现异常的代码
except 异常类型 as e:  # as将异常的类型赋值给变量e,打印e可以知道错误的具体原因
    如果代码检测出现异常,就执行这里的代码

如下出现异常的代码,可以使用异常处理进行处理:

try:
    print('异常检查开始')
    print(name)
    print('异常检查结束')
except NameError as e: 
    print(f'出现异常了{e}')
print('程序没有终止,其他代码继续执行')

# 上述代码执行结果
异常检查开始
出现异常了name 'name' is not defined
程序没有终止,其他代码继续执行

如果被检查的代码块中有可能出现不同类型的异常时,针对不同类型的异常如果想分别用不同的逻辑处理可以使用多个分支的except,类似于多分支的elif,语法如下:

try:
    需要检查是否会出现异常的代码
except NameError:
    检测到NameError时执行的代码
except KeyError:
    检测到KeyError时执行的代码
except ...
    ...

比如下述代码:

def transfer_int(info):
    
    try:
        res = int(info)
    except ValueErro as e:
        print(f'valueerror {e}')
        res = 'valueerror'
    except TypeError as e:
        print(f'typeerror {e})
        res = typeerror
    return res
              
transfer_info('python')  # ValueError: invalid literal for int() with base 10: 'python'
transfer_int({'x': 1})  # TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'

如果多种异常想用同一种逻辑进行处理,可以将多种异常放入元组中,用一个except分支进行处理。

try:
    需要检查是否会出现异常的代码
except (NameError, ValueError, IndexError):
    代码中出现元组中三种异常时执行这里的代码

有小伙伴有疑问了,难道我需要把程序运行中可能出现的异常都需要写一遍了,也太麻烦了吧。为了方便开发人员处理异常,python提供了一种万能异常类型Exception,可以捕获所有的异常:

try:
    需要检查是否会出现异常的代码
except (NameError, ValueError, IndexError):
    代码中出现元组中三种异常时执行这里的代码
except Exception:
    出现其他类型异常统一使用这里的逻辑

比如下述代码:

print('start...')

try:
    print('1111111111')
    l = ['aaa', 'bbbb']
    l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
    print('2222222222')
    xxx
    print('33333333')
    dic = {'a': 1}
    dic['aaa']
# except (IndexError, NameError) as e:
#     print('异常的信息: ', e)
# except KeyError as e:
#     print('字典的key不存在: ', e)
except Exception as e:  # 万能异常
    print('所有异常都可以匹配的到')
print('end....')

使用异常处理时后面还可以跟一个else,但是else必须跟在except之后,不能但粗存在,当需要检查的代码没有触发任何异常的情况下就会执行else下的代码块。

try:
    print('1111111111')
    l = ['1',2]
    print('2222222222222')
    print(33333333333)
except IndexError as e:  # 不执行这里的代码块,else才会执行
    print('异常信息',e)

# 只有再try内代码没有异常才会执行else
else:
    print('我是else')

此外,try还可以与finally使用,从语法上说finally必须放在else之后,但是可以使用try-except-finaally的语法格式,也可以直接使用try-finally的格式,无论被检测的代码是否出现异常,finally后的代码都会正常执行,因此finally的代码块中可以做一些系统资源回收的操作,比如打开的文件。

print('start...')

try:
    print('1111111111')
    l = ['aaa', 'bbbb']
    l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
    print('2222222222')
    xxx
    print('33333333')
    dic = {'a': 1}
    dic['aaa']
    f = open(r'a.txt', 'r', encoding='utf-8')
finally: # 不处理异常,无论是否发生异常都会执行finally的子代码
    print('====》》》》》应该把被检测代码中回收系统资源的代码放到这里')
    if f:
        f.close()

print('end....')

raise - 手动触发异常

对于不符合python解释器的语法或者逻辑的代码,在运行时,python解释器会主动抛出异常,如果在程序运行中,出现了违反了开发人员自定义的各种逻辑或者规则,开发人员也可以自己明确的触发异常,可以使用raise关键字手动触发异常,raise后面必须是一个异常的类或者异常的实力。

class Info():
    
    def __init__(self, name):
        if type(name) is not str:
            raise TypeError('name must be str')
        self.name = name
        
i = Info(123)  #  # TypeError: name must be str

异常处理的使用场景

为了提高程序的健壮性,很多刚入门编程的小伙伴认为应该为程序尽可能的多加try...except,这是一种过度使用异常处理的方式,是不正确的 ,对于可以预知可能会触发异常的条件,建议使用if分支进行判断;而对于一些无法预知的、有很大几率会触发异常的条件才进行异常处理,比如下载时网络出现问题,这种情况无法预知只能使用异常处理。

结语

文章首发于微信公众号程序媛小庄,同步于掘金知乎

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)

异常处理