异常处理

1. 什么是异常

  • 异常就是非正常状态,在Python中使用异常对象来表示异常。若程序在编译或运行过程中发生错误,程序的执行过程就会发生改变,抛出异常对象,程序流进入异常处理。如果异常对象没有被处理或捕捉,程序就会执行回溯(Traceback)来终止程序。

  • 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下

1.1. 语法错误

<font color='red'>语法错误,根本过不了python解释器的语法检测,必须在程序执行前就改正。</font>

# 语法错误示范一
if

# 语法错误示范二
def test:
    pass

# 语法错误示范三
class Foo
    pass

# 语法错误示范四
print(haha

1.2. 逻辑错误

# TypeError:int类型不可迭代
for i in 3:
    pass

# ValueError
num=input(">>: ") #输入hello
int(num)

# NameError
aaa

# IndexError
l=['egon','aa']
l[3]

# KeyError
dic={'name':'egon'}
dic['age']

# AttributeError
class Foo:pass
Foo.x

# ZeroDivisionError:无法完成计算
res1=1/0
res2=1+'str'

2. 异常类型

在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,<font color='red'>一个异常标识一种错误</font>。

2.1. 常见异常

异常 描述
AttributeError 对象没有这个属性,试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
IOError 输入/输出操作失败;基本上是无法打开文件
ImportError 导入模块/对象失败,无法引入模块或包;基本上是路径问题或名称错误
IndexError 序列中没有此索引(index),下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 映射中没有这个键,试图访问字典里不存在的键
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量,试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
SyntaxError Python 语法错误
IndentationError 缩进错误,语法错误(的子类) ;代码没有正确对齐
TypeError 对类型无效的操作
ValueError 传入无效的参数,传入一个调用者不期望的值,即使值的类型是正确的

2.2. 其他异常

异常 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
OSError 操作系统错误
WindowsError 系统调用失败
LookupError 无效数据查询的基类
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

Exception类:是通用异常基类下列异常类均继承于Exception类,python解析器会自动将通用异常类型名称放在内建命名空间中,所以当使用通用异常类型时,不需要import exceptions模块。

3. 异常处理

为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理

3.1. 提前预防

如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防

AGE = 10
while True:
    age = input('>>: ').strip()
    # 只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
    if age.isdigit():
        age = int(age)
        if age == AGE:
            print('you got it')
            break

执行结果:

>>: nick
>>: sdkf
>>: 2
>>: 10
you got it

3.2. 之后预防

如果错误发生的条件是不可预知的,则需要用到 try...except:在错误发生之后进行处理

# 基本语法为
try:
    被检测的代码块
except 异常类型:
    try中一旦检测到异常,就执行这个位置的逻辑

举例

try:
    f = [
        'a',
        'a',
        'a',
        'a',
        'a',
        'a',
        'a',
    ]
    g = (line.strip() for line in f)
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
except StopIteration:
    f.close()

执行结果:

a
a
a
a
a

异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。

s1 = 'hello'
try:
    int(s1)
except IndexError as e:  # 未捕获到异常,程序直接报错
    print(e)

3.2.1. 捕捉多个异常

方法一:指定一个通用异常,可以捕获多个不同的包含在Exception类中的异常类。

try:
    语句块
except Exception:
    语句块

方法二:在一个except子句后将多个异常作为元组元素列出。

try:
    语句块
except (IOError,ValueError): 
    语句块

方法三:except子句后不带任何异常名称,捕获所有异常

try:
    语句块
except: 
    语句块

3.2.2. 捕获异常try..except..else

注意:except子句的数量没有限制,但使用多个except子句捕获异常时,如果异常类之间具有继承关系,则子类应该写在前面,否则父类将会直接截获子类异常。放在后面的子类异常也就不会执行。
格式:

try:
    可能触发异常的语句块
except [exceptionType]:
    捕获可能触发的异常[可以指定处理的异常类型]
except [exceptionType][,date]:
    捕获异常并获取附加数据
except:
    没有指定异常类型,捕获任意异常
else:
    没有触发异常时,执行的语句块

3.2.3. 万能异常Exception

s1 = 'hello'
try:
    int(s1)
except Exception as e:
    print(e)

3.2.4. 多分支异常与万能异常

  • 如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。

  • 如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。

3.2.5. try..finally语句

无论try语句块中是否触发异常,都会执行finally子句中的语句块,因此一般用于关闭文件或关闭因系统错误而无法正常释放的资源。比如文件关闭,释放锁,把数据库连接返还给连接池等。

import os
def write_test(fileName,content_iterable):
    try:
        pwd = open(fileName,'w')
        for key,value in content_iterable.items():
            pwd.write(key+'\t'+value+'\n')  #传入String类型参数同时加入换行符
    finally:
        pwd.close()
if __name__ == '__main__':
    fileName = '/usr/local/src/pyScript/fileOperation.txt'
    dic = {'name':'Jmilk','age':'23','city':'BJ'}
    if os.path.exists(fileName):
        write_test(fileName,dic)
    else:print('File not exist!')

注意:try..finally与try..except 是可以同时使用的。

In [3]: try:
   ...:     raise
   ...: except Exception:
   ...:     print('error')
   ...:     raise
   ...: finally:
   ...:     print('success')
   ...:
error
success
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-530db52949e7> in <module>()
      1 try:
----> 2     raise
      3 except Exception:
      4     print('error'
      5     raise
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType

NOTE:try…finally 的意义在于,就是我们在 try 代码块中执行了 return 语句,但是仍然会继续执行在 finally 中的代码块,所以我们一般用作处理资源的释放。

3.2.6. 异常的最终执行

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
#except Exception as e:
#    print(e)
else:
    print('try内代码块没有异常则执行我')
finally:
    print('无论异常与否,都会执行该模块,通常是进行清理工作')

3.2.7. try...except总结

  1. 把错误处理和真正的工作分开来
  2. 代码更易组织,更清晰,复杂的工作任务更容易实现;
  3. 毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;

4. try的工作原理

执行一个try语句时,python解析器会在当前程序流的上下文中作标记,当出现异常后,程序流能够根据上下文的标记回到标记位,从而避免终止程序。

  1. 如果try语句执行时发生异常,程序流跳回标记位,并向下匹配执行第一个与该异常匹配的except子句,异常处理完后,程序流就通过整个try语句(除非在处理异常时又引发新的异常)。
  2. 如果没有找到与异常匹配的except子句(也可以不指定异常类型或指定同样异常类型Exception,来捕获所有异常),异常被递交到上层的try(若有try嵌套时),甚至会逐层向上提交异常给程序(逐层上升直到能找到匹配的except子句。实在没有找到时,将结束程序,并打印缺省的错误信息)。
  3. 如果在try子句执行时没有发生异常,python将执行else语句后的语句(可选),然后控制流通过整个try语句。
try:
    openFile = open('notExistsFile.txt','r')
    fileContent = openFile.readlines()
except IOError:   
    print('File not Exists')        # 执行
except: 
    print('process exception')      # 不执行
else:
    print('Reading the file')       # 不执行

执行结果:

In [157]: %run testError.py
File not Exists

嵌套try:

try:
    try:
        openFile = open('notExistsFile.txt','r')
        fileContent = openFile.readlines()
    except IOError:
        print('File not Exists')      #执行
except:
    print('process exception')        #不执行 
else: 
    print('Reading the file')         #执行

执行结果:

In [159]: %run testError.py
File not Exists
Reading the file

5. 自定义异常

通过(直接或简介)继承Exception类来创建一个自定义异常类,自定义的异常类只能通过raise关键字来手动触发。

class testError(Exception):    #直接集成Exception类
    def __init__(self,arg):
        self.args = arg
try:
    raise testError('Just test')
except testError,info:
    print info.args

执行结果:

In [52]: %run test.py
('J', 'u', 's', 't', ' ', 't', 'e', 's', 't')

with..as触发异常自动关闭资源
在使用类文件的流对象时,都需要单独的调用close()来关闭资源。with..as语句能够实现在with语句块执行完后,自动的关闭文件。如果with语句块中触发异常,会调用默认的异常处理器处理,而且文件仍然能够正常关闭。

import os
def testWith(fileName):
    try:
        with open(fileName,'r+') as pwd:
            pwd.readlines()
            print 2/0
    except Exception:
            print('File closed:',pwd.closed  #判断文件是否关闭
if __name__ == '__main__':
    if os.path.exists('/usr/local/src/pyScript/fileOperation.txt'):
        testWith('/usr/local/src/pyScript/fileOperation.txt')
        print('continue')

执行结果:

In [17]: %run test.py
File closed: True    #没有call close()函数,文件仍然自动关闭。
continue

6. as获取异常信息

每个异常都会有一定的描述信息,可以通过as关键字来获取。但是这种异常信息并不适合一般用户阅读,所以会使用自定义的异常信息。但是仍然会将原有的异常信息保留起来,用于后期的异常分析。

try:
    try:
        openFile = open('notExistsFile.txt','r')
        fileContent = openFile.readlines()
    except (IOError,ValueError) as info:  #或者except (IOError,ValueError),info: 
        print info
except:
    print('process exception')
else:
    print('Reading the file')

执行结果:

In [164]: %run testError.py
[Errno 2] No such file or directory: 'notExistsFile.txt'

异常参数
也可以使用异常参数作为输出的异常信息参数,来获取异常信息。并且异常参数中包含有异常信息、错误数字、错误位置等属性。

try:
    try:
        openFile = open('notExistsFile.txt','r')
        fileContent = openFile.readlines()
    except (IOError,ValueError),info:
        print dir(info)
        print info.args
except:
    print('process exception')
else:
    print('Reading the file')

执行结果:

In [44]: %run test.py
['__class__', '__delattr__', '__dict__', 
'__doc__', '__format__', '__getattribute__', '__getitem__', 
'__getslice__', '__hash__', '__init__', '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', '__setattr__', '__setstate__', 
'__sizeof__', '__str__', '__subclasshook__', '__unicode__', 'args', 
'errno', 'filename', 'message', 'strerror']
(2, 'No such file or directory')
Reading the file

7. traceback追踪异常

使用traceback追踪异常的时候,需要import traceback模块。traceback模块可以有效的帮助查看异常的详细信息。
注意:若希望获取异常的详细信息,却又不会终止程序的执行,可以在except子句中使用

tarceback.print_exc()函数。 
tarceback.print_exc(): 
print_exc(limit=None, file=None) 
Shorthand for ‘print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)’. 
(In fact, it uses sys.exc_info() to retrieve the same information in a thread-safe way.)

输出sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file等异常信息,实际上是以线程安全的方式去使用sys.exc_info()函数来获取相同的信息。

import traceback
try:
    openFile = open('notExistsFile.txt','r')
    fileContent = openFile.readlines()
except IOError as info:
    print('File not Exists')
    print(info)
    traceback.print_exc()
    print('continue')
except:
    print('process exception')
else:
    print('Reading the file')

执行结果:

In [38]: %run test.py
File not Exists
[Errno 2] No such file or directory: 'notExistsFile.txt'
Traceback (most recent call last):
  File "/usr/local/src/pyScript/test.py", line 5, in <module>
    openFile = open('notExistsFile.txt','r')
IOError: [Errno 2] No such file or directory: 'notExistsFile.txt'
continue

异常信息的重定向:如果希望将异常的信息保存在一个指定的文件中,以供后期分析。可以使用下面的方法:

import traceback
try:
    with open('notExistsFile.txt','r') as openFile:
        fileContent = openFile.readlines()
except IOError:
    with open('errorLog','w+') as errorInfo:
        traceback.print_exc(file=errorInfo)
    print('continue')
except:
    print('process exception')
else:
    print('Reading the file')

执行结果:

In [61]: %run test.py
continue
In [62]: cat errorLog
Traceback (most recent call last):
  File "/usr/local/src/pyScript/test.py", line 5, in <module>
    with open('notExistsFile.txt','r') as openFile:
IOError: [Errno 2] No such file or directory: 'notExistsFile.txt'

sys.exc_info()获取异常信息
traceback.print_exc()函数实际上是call sys.exc_info()

import sys  
try:  
    a=b  
    b=c  
except:  
    info=sys.exc_info()  
    print(info[0],":",info[1])

执行结果:

In [65]: %run test.py
<type 'exceptions.NameError'> : name 'b' is not defined

异常处理用于处理程序错误之外,还有许多应用的地方。如关闭资源、平台兼容、模块导入等。

8. 触发异常raise

raise关键字:手动抛出一个通用的异常类型(Exception),类似Java中的throw语句。raise关键字后跟异常的名称,异常名称能够标识出异常类的对象。执行raise语句时,python会创建指定异常类的对象,还能够指定对异常对象进行初始化的参数,参数也可以为由若干参数组成的元组。
注意:一旦执行raise语句,程序就会被终止。
格式:raise [exceptionType[,argument][,traceback]]

def testRaise(number):
    if number < 1:
        raise ValueError('Invalid value') # 或者 raise ValueError,'Invalid value'
testRaise(0)

traceback:这个参数用于追踪异常对象,一般很少使用。
这样就可以触发一个异常,并且接收异常信息。

9. 传递异常

当你捕获到异常之后又希望再次的触发异常只需要使用不带任何参数的raise关键字。

import os
try:
    openFile = open('notExistsFile.txt','r')
    fileContent = openFile.readlines()
except IOError:
    print('File not Exists'
    if not os.path.exists('notExistsFile.txt'):
        raise
except:
    print('process exception')

异常会在捕获之后再次触发同一个异常。

10. assert语句触发异常

assert语句根据后面的表达式的真假来控制程序流。若为True,则往下执行。若为False,则中断程序并调用默认的异常处理器,同时输出指定的提示信息。

格式:assert expression,'information'

例如:

def testAssert(x):
    assert x < 1,'Invalid value'
testAssert(1)
print('Valid value')

执行结果:

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

推荐阅读更多精彩内容