学习:异常处理

转自:http://www.byhy.net/tut/py/basic/19/

异常对象

点击这里,边看视频讲解,边学习以下内容

请大家运行如下代码

a = 100/0
print(a)

就会发现解释器显示如下的错误提示

Traceback (most recent call last):
  File "xxxxxxxxxx.py", line 1, in <module>
    a = 100/0
ZeroDivisionError: division by zero

大家要学会看解释器的报错。

这就是解释器向我们报告, 有一个 ZeroDivisionError 错误对象 或者说 异常对象 产生了。

这个 ZeroDivisionError 对象 代表的是一个除以0 的异常。 我们知道0是不能作为除数的。

因为这个问题,解释器没有办法继续执行后面的代码了。所以程序就此结束执行了。

ZeroDivisionError就是一个异常对象的类,继承自标准库里面的 Exception 类。

Python标准库中还有很多其他的异常类 都是继承自标准库里面的 Exception 类,代表各种不同类型的错误。

大家可以在命令行窗口 运行 Python 解释器交互命令行,分别输入如下代码:

xxxx

会产生 NameError,表示xxxx没有定义


dict1 = {1:1}
print(dict1[2])

会产生 KeyError,表示该字典没有key为2的元素


import xxxx

会产生 ModuleNotFoundError,表示找不到xxxx这样的模块

捕获异常

点击这里,边看视频讲解,边学习以下内容

解释器执行代码过程中,如果发生异常,就会导致解释器没法继续按照正常流程往下执行代码,所以解释器会结束当前代码的执行。

如果我们在编码的时候,就预料到了某些代码运行时可能出现某些异常,就可以使用 try...except... 这样的方法来捕获和处理异常。

比如,我们要开发程序,实现一个把用户输入的路程长度从英里换算成公里,如下所示

while True:
    miles = input('请输入英里数:')
    km = int(miles) * 1.609344
    print(f'等于{km}公里')

编写这段代码的时候, 我们就可以预料到,可能用户会输入非数字的字符,这样用int转化就会出错,导致整个程序就退出了。

这时,我们就可以这样写

while True:
    try:
        miles = input('请输入英里数:')
        km = int(miles) * 1.609344
        print(f'等于{km}公里')
    except ValueError:
        print('你输入了非数字字符')

try 下面缩进的3行代码可以看成是 保护区 中的代码。

如果执行保护区中代码时,出现异常,解释器会结束保护区中后续代码的执行,并检查这个异常的类型是否匹配后面的except 语句中声明的类型。

如果匹配上,解释器知道程序对此种异常是预料到的,并且有对应的处理方案,也就是匹配的except下面缩进的代码。解释器就执行匹配的except下面缩进的代码,不会因此中止程序。

上面的例子中,执行 try 下面缩进的代码时,如果用户输入了 hello 这样的非数字, 就会在这行语句处

km = int(miles) * 1.609344

产生 ValueError 类型的异常, 解释器就会去查看后面的 except 语句是否声明了对 ValueError 异常的处理。

发现有, 就会执行后面缩进的代码。也就是这句代码

print('你输入了非数字字符')

except 后面缩进的代码 就是对这种类型错误 的一种处理

既然程序已经知道如何处理这种问题, 就不需要结束执行,只需要执行完 处理代码后, 进行原来正常的执行流程。

在这里,就是继续 while True 循环。

如果我们开发程序的时候,估计某个代码段中可能出现好几种类型的异常,可以使用多个except 代码段,分别捕获多种类型的异常,如下

try:
    choice = input('输入你的选择:')
    if choice == '1':
        100/0
    elif choice == '2':
        [][2]
except ZeroDivisionError:
    print ('出现 ZeroDivisionError')
except IndexError  :
    print ('出现 IndexError')

如果 输入'1', 则会产生 ZeroDivisionError 异常, 就会被 except ZeroDivisionError 捕获,执行对应的代码

print ('出现 ZeroDivisionError')

如果 输入'2', 则会产生 IndexError 异常, 就会被 except IndexError 捕获,执行对应的代码

print ('出现 IndexError')

获取异常对象

我们使用except 语句匹配异常类型的时候, 可以使用as关键字,后面加一个变量名,如下所示:

try:
    100/0
except ZeroDivisionError as e:
    print (f'异常对象信息:{e}')

这样,运行代码的时候,当try中的语句产生异常对象时,就会 把产生的异常对象赋值给as后的变量。

上面的代码,运行输出

异常对象信息:division by zero

产生的异常对象赋值给了变量 e。

这样我们就可以在后续的代码中得到产生的异常对象的信息。

匹配所有异常

如果我们在写一段代码的时候,不知道这段代码会抛出什么样的异常,并且我们不希望程序因为异常而中止。

这时我们可以匹配所有类型的异常,这样任何类型的异常发生都不会终止程序了。 如下:

try:
    100/0
except Exception as e:
    print('未知异常:', e)

因为所有的异常都是 Exception 的子类。 所以 Exception能匹配所有类型的异常。

还有一种更简洁的写法,也可以匹配所有类型的异常,如下所示

try:
    100/0
except:
    print('未知异常:')

except 后面 没有写异常的类型,也表示匹配所有的异常。

如果我们想知道异常的详细信息,可以使用traceback模块,如下,

import traceback

try:
    100/0
except :
    print(traceback.format_exc())

在except下面缩进代码中,使用traceback模块里面的format_exc函数,可以显示异常的信息 和 异常产生处的函数调用栈的信息。

上面的代码会打印出导致异常的详细的函数调用栈的信息,如下

Traceback (most recent call last):
  File "xxxx/xxx.py", line 4, in <module>
    100/0
ZeroDivisionError: division by zero

自定义异常

点击这里,边看视频讲解,边学习以下内容

异常类型都是 继承自Exception的类,表示各种类型的错误。

我们也可以自己定义异常,比如我们写一个用户注册的函数, 要求用户输入的电话号码只能是中国的电话号码,并且电话号码中不能有非数字字符。

可以定义下面这两种异常类型:

# 异常对象,代表电话号码有非法字符
class InvalidCharError(Exception):
    pass

# 异常对象,代表电话号码非中国号码
class NotChinaTelError(Exception):
    pass

定义了上面的异常,当用户输入电话号码时,出现相应错误的时候,我们就可以使用raise 关键字来抛出对应的自定义异常

def  register():
    tel = input('请注册您的电话号码:')

    # 如果有非数字字符
    if not tel.isdigit(): 
        raise InvalidCharError()

    # 如果不是以86开头,则不是中国号码
    if not tel.startswith('86'): 
        raise NotChinaTelError()

    return tel

try:
    ret = register()
except InvalidCharError:
    print('电话号码中有错误的字符')
except NotChinaTelError:
    print('非中国手机号码')

函数调用里面产生的异常

点击这里,边看视频讲解,边学习以下内容

代码1

大家来看下面的一段代码:

def level_3():
    print ('进入 level_3')
    a = [0]
    b = a[1]
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    level_3()
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    level_2()
    print ('离开 level_1')

level_1()

print('程序正常退出') 

运行该代码会得到类似下面的结果

进入 level_1
进入 level_2
进入 level_3
Traceback (most recent call last):
  File "E:\err.py", line 18, in <module>
    level_1()
  File "E:\err.py", line 14, in level_1
    level_2()
  File "E:\err.py", line 9, in level_2
    level_3()
  File "E:\err.py", line 4, in level_3
    b = a[1]
IndexError: list index out of range

函数调用次序是这样的

主体部分调用 函数 level_1

函数level_1调用 函数level_2

函数level_2调用 函数level_3

大家注意:函数 level_3 中有个 列表索引越界的错误。

所以执行到该函数的时候,解释器报错了。它在终端上显示了错误代码的具体位置。 也就是

File "E:\err.py", line 4, in level_3
    b = a[1]

大家可以发现,上面还有输出的信息,说明了这行引起异常的代码, 是怎样被 一层层 的调用进来的。

这就是函数调用栈的信息。

当异常在函数中产生的时候,解释器会终止当前代码的执行, 查看当前函数是否 声明了该类型异常的 except 处理,如果有,就执行, 随后继续执行代码。

如果当前函数没有 声明了该类型异常的处理, 就会中止当前函数的执行,退出到调用该函数的上层函数中, 查看上层是否有 声明了该类型异常的 except 处理。如果有,就执行该异常匹配处理。 随后继续执行代码。

如果上层函数也没有 该类型异常的匹配处理, 就会到继续到再上层的函数查看是否有 该类型异常的匹配处理。

如此这般,直到到了最外层的代码。 如果依然没有 声明了该类型异常处理,就终止当前代码的执行。


下面的几个示例代码,分别在不同的函数调用层次 捕获异常。 大家可以依次执行一下,看看各自对执行结果有什么影响。

代码2

import traceback

def level_3():
    print ('进入 level_3')
    a = [0]
    b = a[1]
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    level_3()
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    level_2()
    print ('离开 level_1')

try:
    level_1()
except :
    print(f'未知异常:{traceback.format_exc()}')

print('程序正常退出')

代码3

import traceback

def level_3():
    print ('进入 level_3')
    a = [0]
    b = a[1]
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    level_3()
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    try:
        level_2()
    except :
        print(f'未知异常:{traceback.format_exc()}')
    print ('离开 level_1')

level_1()

print('程序正常退出')

代码4

import traceback

def level_3():
    print ('进入 level_3')
    a = [0]
    b = a[1]
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    try:
        level_3()
    except :
        print(f'未知异常:{traceback.format_exc()}')
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    level_2()
    print ('离开 level_1')

level_1()

print('程序正常退出')

代码5

import traceback

def level_3():
    print ('进入 level_3')
    try:
        a = [0]
        b = a[1]
    except :
        print(f'未知异常:{traceback.format_exc()}')
    print ('离开 level_3')

def level_2():
    print ('进入 level_2')
    level_3()
    print ('离开 level_2')

def level_1():
    print ('进入 level_1')
    level_2()
    print ('离开 level_1')

level_1()

print('程序正常退出')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容