Python 中(至少)有两种错误:语法错误和异常( syntax errors 和 exceptions )。
(1)语法错误,也被称作解析错误,无法通过python解释器的语法检测,必须在程序执行前就改正。比如:
>>> while True print('Hello world')
File "", line 1, in ?
while True print('Hello world')
^
SyntaxError: invalid syntax
语法分析器指出错误行,并且在检测到错误的位置前面显示一个小“箭头”。 错误是由箭头 前面 的标记引起的(或者至少是这么检测的): 这个例子中,函数 print() 被发现存在错误,因为它前面少了一个冒号( ':'
)。 错误会输出文件名和行号,所以如果是从脚本输入的你就知道去哪里检查错误了。
什么是异常?
异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常是Python对象,表示一个错误。即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为 异常,并且程序不会无条件的崩溃。所以我们需要异常的处理来解决这些问题。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。
异常也有不同的类型,异常类型做为错误信息的一部分显示出来:零除错误( ZeroDivisionError ) ,命名错误( NameError) 和 类型错误( TypeError )。打印错误信息时,异常的类型作为异常的内置名显示。对于所有的内置异常都是如此,不过用户自定义异常就不一定了(尽管这是一个很有用的约定)。标准异常名是内置的标识(没有保留关键字)。后一部分是关于该异常类型的详细说明,这意味着它的内容依赖于异常类型。错误信息的前半部分以堆栈的形式列出异常发生的位置。通常在堆栈中列出了源代码行,然而,来自标准输入的源码不会显示出来。
python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误。你可以使用该功能来调试python程序。
如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(traceback)终止执行。
python2.x捕获异常语法(只能py2):
try:
...some functions...
except Exception, e:
print(e)
python3.x捕获异常语法(py2也可以,推荐使用,有兼容能力,性能更好):
try:
...some functions...
except Exception as e:
print(e)
捕获异常可以使用try/except语句。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。如果你不想在异常发生时结束你的程序,只需在try里捕获它。
语法:
以下为简单的try....except...else的语法(可以有多个罗列的except子句,也可以在一个except中写多个捕获类型):
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
else子句只能出现在所有 except 子句之后。当 try 语句没有抛出异常时,需要执行一些代码,可以使用这个子句。
示例:
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
print "Error: 没有找到文件或读取文件失败"
else:
print "内容写入文件成功"
fh.close()
如果正常执行:
$ python test.py
内容写入文件成功
$ cat testfile # 查看写入的内容
这是一个测试文件,用于测试异常!!
如果写文件没有权限:
$ python test.py
Error: 没有找到文件或读取文件失败
你可以不带任何异常类型使用except,如下实例:
try:
正常的操作
......................
except:
发生异常,执行这块代码
......................
else:
如果没有异常执行这块代码
以上方式try-except语句捕获所有发生的异常。但这不是一个很好的方式,我们不能通过该程序识别出具体的异常信息。因为它捕获所有的异常,除非不打印错误信息,只是为了捕获异常。
一个 try 语句可能包含多个 except 子句,分别指定处理不同的异常。至多只会有一个分支被执行。异常处理程序只会处理对应的 try 子句中发生的异常,在同一个 try 语句中,其他子句中发生的异常则不作处理。一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组。一个 except 子句可以在括号中列出多个异常的名字,如下所示:
try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
发生以上多个异常中的一个,执行这块代码
......................
else:
如果没有异常执行这块代码
除了以上做法可能可读性差,不能简洁明了告诉我们每一个不同的异常的处理逻辑,所以我们可以使用多个except子句的如下方式 ,类似于多个catch子句:
number = input('please type a number(max=100):')
try:
number = int(number)
number = number + 1
except ValueError as e:
print('Error: {}'.format(e))
except IOError as e:
print('IOError: {}'.format(e))
finally:
print("That's all!")
注意:except子句的数量没有限制,但使用多个except子句捕获异常时,如果异常类之间具有继承关系,则子类应该写在前面,否则父类将会直接截获子类异常。放在后面的子类异常也就不会执行。 但最多只有一个分支会被执行,所以except子句有排序先后问题
try 语句按如下方式工作:
首先,执行 try 子句 (在 try 和 except 关键字之间的部分)。
如果没有异常发生, except 子句 在 try 语句执行完毕后就被忽略了。
如果在 try 子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。
如果异常匹配于 except 关键字后面指定的异常类型,就执行对应的except子句。然后继续执行 try 语句之后的代码。
如果发生了一个异常,在 except 子句中没有与之匹配的分支,它就会传递到上一级 try 语句中。
如果最终仍找不到对应的处理语句,它就成为一个 未处理异常,终止程序运行,显示提示信息。
try-finally 语句无论是否发生异常都将执行最后的代码finally里的代码。
NOTE:try…finally 的意义在于,就是我们在 try 代码块中执行了 return 语句,但是仍然会继续执行在 finally 中的代码块,所以我们一般用作处理资源的释放,但不捕获异常。
基本语法:
try:
<语句>
finally:
<语句> #退出try时总会执行
raise
实例:
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "Error: 没有找到文件或读取文件失败"
如果打开的文件没有可写权限,输出如下所示:
$ python test.py
Error: 没有找到文件或读取文件失败
同样的例子也可以写成如下方式:
try:
fh = open("testfile", "w")
try:
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "关闭文件"
fh.close()
except IOError:
print "Error: 没有找到文件或读取文件失败"
当在try块中抛出一个异常,立即执行finally块代码。
finally块中的所有语句执行后,异常被再次触发,并执行except块代码。参数的内容不同于异常。
当出现类似break,return等关键字,会先执行完finally块中的语句再进行break等操作。
完整版的语句块:
def div(a, b):
try:
print(a / b)
except ZeroDivisionError:
print("Error: b should not be 0 !!")
except Exception as e:
print("Unexpected Error: {}".format(e))
else:
print('Run into else only when everything goes well')
finally:
print('Always run into finally block.')
# tests
div(2, 0)
div(2, 'bad type')
div(1, 2)
Error: b should not be 0 !!
Always run into finally block.
Unexpected Error: unsupported operand type(s) for /: 'int' and 'str'
Always run into finally block.
0
Run into else only when everything goes well
Always run into finally block.
# Mutiple exception in one line
try:
print(a / b)
except (ZeroDivisionError, TypeError) as e:
print(e)
# Except block is optional when there is finally
try:
open(database)
finally:
close(database)
# catch all errors and log it
try:
do_work()
except:
# get detail from logging module
logging.exception('Exception caught!')
# get detail from sys.exc_info() method
error_type, error_value, trace_back = sys.exc_info()
print(error_value)
raise
except
语句不是必须的,finally
语句也不是必须的,但是二者必须要有一个,否则就没有try
的意义了。except
语句可以有多个,Python会按except
语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的except
语句。类似于catch语句,按照顺序匹配,所以保证最上层异常类型最小。不需要下层异常类型包含上层,但不能够出现上层异常类型包含下层的情况,此时下层异常类型永远无法捕获。except
语句可以以元组形式同时指定多个异常,参见实例代码。并且指定一个对象相当于Exception e。except
语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过logging或者sys模块获取当前异常。raise
,后面不要带任何参数或信息。类似于throws往上抛出。try/except
语句,比如with
语句,getattr()
方法。一个异常可以带上参数,可作为输出的异常信息参数。具体需要捕获异常类型和异常类型的错误提示。类似于Exception e的格式。这个参数是否存在、是什么类型,依赖于异常的类型。
你可以通过except语句来捕获异常的参数,如下所示:
Python2中:
try:
正常的操作
......................
except ExceptionType, Argument:
你可以在这输出 Argument 的值...
注:在Python3中,原Python2的except Exception , ex
的别名方法已经不能使用,逗号被认为是两种异常的分隔符,而不是取别名。
Python2和Python3中:Py3不支持直接输出参数的格式。
try:
正常的操作
......................
except ExceptionType as e:
你可以在这输出 e 的值...
变量接收的异常值通常包含在异常的语句中。在元组的表单中变量可以接收一个或者多个值。元组通常包含错误字符串,错误数字,错误位置。
实例:
以下为单个异常的实例:
# 定义函数
def temp_convert(var):
try:
return int(var)
except ValueError, Argument:
print "参数没有包含数字\n", Argument
# 调用函数
temp_convert("xyz")
以上程序执行结果如下:
$ python test.py
参数没有包含数字
invalid literal for int() with base 10: 'xyz'
Python3中执行:不支持这种方式。
File "untitled1.py", line 12
except ValueError, Argument:
^
SyntaxError: invalid syntax
Python 3只支持这样方式,不支持后接参数的形式,必须指定为一个参数。所以为了兼容Py2和Py3,需要用下述形式,而且功能更强大。
# 定义函数
def temp_convert(var):
try:
return int(var)
except ValueError as e:
print "参数没有包含数字\n", Argument
# 调用函数
temp_convert("xyz")
这样做是为 except 子句指定一个变量。这个变量绑定于一个异常实例,它存储在 instance.args
的参数中。为了方便起见,异常实例定义了 __str__() ,这样就可以直接访问过打印参数而不必引用 .args
。
python中try except处理程序异常的三种常用方法
方法一:捕获所有异常
try:
a=b
b=c
except (ZeroDivisionError,Exception):
print(ZeroDivisionError,":",Exception)
方法二:采用traceback模块查看异常
#引入python中的traceback模块,跟踪错误
import traceback
try:
a=b
b=c
except:
traceback.print_exc()
方法三:采用sys模块回溯最后的异常
#引入sys模块
import sys
try:
a=b
b=c
except:
info=sys.exc_info()
print info[0],":",info[1]
但是,如果你还想把这些异常保存到一个日志文件中,来分析这些异常,那么请看下面的方法:
把 traceback.print_exc() 打印在屏幕上的信息保存到一个文本文件中
输出sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file等异常信息,实际上是以线程安全的方式去使用sys.exc_info()函数来获取相同的信息。
import traceback
try:
a=b
b=c
except:
f=open("c:log.txt",'a')
traceback.print_exc(file=f)
f.flush()
f.close()
我们可以使用raise语句自己触发异常。类似于Java的thow和thows(直接抛出自定义异常)主动抛出异常。此时可以让上层调用者捕获和处理该异常。更多的时候是抛出自定义类型。
raise唯一的一个参数指定了要被抛出的异常的实例,如果什么参数都不给,那么会默认抛出当前异常。
raise语法格式如下:
raise [Exception [, args [, traceback]]]
语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。
最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。
作用:
(1)需要记录错误信息,然后将异常继续往上层传递,让上层去处理异常(类似于Java的throws)。
(2)需要主动弹出异常,作为警告或特殊处理(类似于Java的throw)。
实例:
一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的类,这是一个类的实例的参数。
定义一个异常非常简单,如下所示:
def functionName( level ):
if level < 1:
raise Exception("Invalid level!", level)
# 触发异常后,后面的代码就不会再执行
注意:为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串。
例如我们捕获以上异常,"except"语句如下所示:
try:
正常逻辑
except Exception,err:
触发自定义异常
else:
其余代码
实例:
# 定义函数
def mye( level ):
if level < 1:
raise Exception,"Invalid level!"
# 触发异常后,后面的代码就不会再执行
try:
mye(0) # 触发异常
except Exception,err:
print 1,err
else:
print 2
执行以上代码,输出结果为:
$ python test.py
1 Invalid level!
一般为了形成捕获链,重新抛出捕获的异常,都是用raise,不加参数。
通过创建一个新的异常类,程序可以命名它们自己的异常。异常应该是典型的继承自Exception类,通过直接或间接的方式。异常的名字都以Error
结尾,我们在为自定义异常命名的时候也需要遵守这一规范,就跟标准的异常命名一样。
以下为与RuntimeError相关的实例,实例中创建了一个类,基类为RuntimeError,用于在异常触发时输出更多的信息。
异常类中可以定义任何其它类中可以定义的东西,但是通常为了保持简单,只在其中加入几个属性信息,以供异常处理句柄提取。如果一个新创建的模块中需要抛出几种不同的错误时,一个通常的作法是为该模块定义一个异常基类,然后针对不同的错误类型派生出对应的异常子类。
在try语句块中,用户自定义的异常后执行except块语句,变量 e 是用于创建Networkerror类的实例。
class Networkerror(RuntimeError):
def __init__(self, arg):
self.args = arg
在你定义以上类后,你可以触发该异常,如下所示:
try:
raise Networkerror("Bad hostname")
except Networkerror,e:
print e.args
Python解析器会自动将通用异常类型名称放在内建命名空间中。
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
异常类型组织结构如下:
1 BaseException 2 +-- SystemExit 3 +-- KeyboardInterrupt 4 +-- GeneratorExit 5 +-- Exception 6 +-- StopIteration 7 +-- StandardError 8 | +-- BufferError 9 | +-- ArithmeticError 10 | | +-- FloatingPointError 11 | | +-- OverflowError 12 | | +-- ZeroDivisionError 13 | +-- AssertionError 14 | +-- AttributeError 15 | +-- EnvironmentError 16 | | +-- IOError 17 | | +-- OSError 18 | | +-- WindowsError (Windows) 19 | | +-- VMSError (VMS) 20 | +-- EOFError 21 | +-- ImportError 22 | +-- LookupError 23 | | +-- IndexError 24 | | +-- KeyError 25 | +-- MemoryError 26 | +-- NameError 27 | | +-- UnboundLocalError 28 | +-- ReferenceError 29 | +-- RuntimeError 30 | | +-- NotImplementedError 31 | +-- SyntaxError 32 | | +-- IndentationError 33 | | +-- TabError 34 | +-- SystemError 35 | +-- TypeError 36 | +-- ValueError 37 | +-- UnicodeError 38 | +-- UnicodeDecodeError 39 | +-- UnicodeEncodeError 40 | +-- UnicodeTranslateError 41 +-- Warning 42 +-- DeprecationWarning 43 +-- PendingDeprecationWarning 44 +-- RuntimeWarning 45 +-- SyntaxWarning 46 +-- UserWarning 47 +-- FutureWarning 48 +-- ImportWarning 49 +-- UnicodeWarning 50 +-- BytesWarning
捕捉到了异常,但是又想重新抛出它(传递异常),使用不带参数的raise
语句即可:
def f1():
print(1/0)
def f2():
try:
f1()
except Exception as e:
raise # don't raise e !!!
f2()
在Python2中,为了保持异常的完整信息,那么你捕获后再次抛出时千万不能在raise
后面加上异常对象,否则你的trace
信息就会从此处截断。以上是最简单的重新抛出异常的做法,也是推荐的做法。
还有一些技巧可以考虑,比如抛出异常前你希望对异常的信息进行更新。
def f2():
try:
f1()
except Exception as e:
e.args += ('more info',)
raise
Python3对重复传递异常有所改进,你可以自己尝试一下,不过建议还是遵循以上规则。
当我们要捕获一个通用异常时,应该用Exception
还是BaseException
?这两个异常到底有啥区别呢? 请看它们之间的继承关系。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration...
+-- StandardError...
+-- Warning...
从Exception
的层级结构来看,BaseException
是最基础的异常类,Exception
继承了它。BaseException
除了包含所有的Exception
外还包含了SystemExit
,KeyboardInterrupt
和GeneratorExit
三个异常。
由此看来你的程序在捕获所有异常时更应该使用Exception
而不是BaseException
,因为被排除的三个异常属于更高级别的异常,合理的做法应该是交给Python的解释器处理。
as 表示将异常重命名。
代码示例如下:
try:
do_something()
except NameError as e: # should
pass
except KeyError, e: # should not
pass
在Python2的时代,你可以使用以上两种写法中的任意一种。在Python3中你只能使用第一种写法,第二种写法已经不再支持。第一个种写法可读性更好,而且为了程序的兼容性和后期移植的成本,请你果断抛弃第二种写法。
把字符串当成异常抛出看上去是一个非常简洁的办法,但其实是一个非常不好的习惯。
if is_work_done():
pass
else:
raise "Work is not done!" # not cool
上面的语句如果抛出异常,那么会是这样的:
Traceback (most recent call last):
File "/demo/exception_hanlding.py", line 48, in
raise "Work is not done!"
TypeError: exceptions must be old-style classes or derived from BaseException, not str
这在 Python2.4 以前是可以接受的做法,但是没有指定异常类型有可能会让下游没办法正确捕获并处理这个异常,从而导致你的程序难以维护。简单说,这种写法是过时的,应该摒弃,应该抛出一个具体类型。
Python 本身提供了很多的语法范式简化了异常的处理,比如for
语句就处理了的StopIteration
异常,让你很流畅地写出一个循环。with
语句在打开文件后会自动调用finally
并关闭文件(预定义清理行为)。我们在写 Python 代码时应该尽量避免在遇到这种情况时还使用try/except/finally的思维来处理。
如果with语句块中触发异常,会调用默认的异常处理器处理,而且文件仍然能够正常关闭。
# should not
try:
f = open(a_file)
do_something(f)
finally:
f.close()
# should
with open(a_file) as f:
do_something(f)
再比如,当我们需要访问一个不确定的属性时,有可能你会写出这样的代码:
try:
test = Test()
name = test.name # not sure if we can get its name
except AttributeError:
name = 'default'
其实你可以使用更简单的getattr()
来达到你的目的。
name = getattr(test, 'name', 'default')
在第一个方法中需要在finally子句中再判断f是否为空,然后确定是否关闭。
断言(assert):当程序运行到某个节点的时候,就断定某个变量的值必然是什么,或者是对象必然拥有某个属性等。简单点来说的话,就是断定是什么东西就必然是什么东西,如果不是,就抛出断言错误异常。
断言应该用于如下情况:
在测试用例中,执行完测试用例后,最后一步是判断测试结果是pass还是fail,自动化测试脚本里面一般把这种生成测试结果的方法称为断言。
assert语句根据后面的表达式的真假来控制程序流。若为True,则往下执行。若为False,则中断程序并调用默认的异常处理器,同时输出指定的提示信息。也就是触发一个带可选错误信息的AssertionError。
assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
格式:
assert expression,'information'
Example:
#!/usr/bin/env python
def testAssert(x):
assert x < 1,'Invalid value'
testAssert(1)
print 'Valid value'
output:
AssertionError: Invaild value
以上是基本断言用法。还有一些已经定义好的断言函数可以使用。
常用的断言函数:
(1)可以使用断言去检查代码中的确定量。
确定量怎么理解?在当前情境下,确定量总会满足特定条件。比如,在某段代码里,变量 x 的值始终应该在 0 到 1 之间。
(2)断言能够帮助别人或未来的你理解代码,找出程序中逻辑不对的地方。
一方面,断言会提醒你某个对象应该处于何种状态,另一方面,如果某个时候断言为假,会抛出 AssertionError 异常,很有可能终止程序。
(3)参数校验
有的时候断言会被用到函数参数校验的地方,我们应该避免这种使用方式。因为,如果 Python 以 -O 选项执行的话,assert 语句会被跳过。这样的话,我们的校验语句都会失效。断言为假触发的异常的语义相比于一般的异常也不太明确。比如,当需要使用 int 类型的时候用户传入 string 类型手动触发一个 TypeError ,或是当需要正数但接收到负数的时候手动触发一个 ValueError ,都比 AssertionError好理解一些。
(1)总体来说使用方法类似于Java的try/catch/finally,只不过把catch换成了except。异常类型,参数,自定义异常类,主动抛出异常等Java都有。
(2)有一个异常处理的问题,不管是Python,Java还是C++等都需要考虑。因为本人是学信息安全的,所以很多时候需要考虑安全问题。如果在一个公司的实际的项目里,每一次都打印异常信息,将是一件很危险的事,会给黑客提供很多信息量;而且也不是一个用户友好的开发实现。所以一是保证异常捕获不暴露具体信息,二是要形成一个异常捕获链,传递异常。
(3)避免在catch
语句块中干一些没意义的事情,捕获异常也是需要成本的。
(4)只处理你知道的异常,避免捕获所有异常然后吞掉它们,而你并不知道这个错误。
(5)不要使用异常来控制流程,那样你的程序会无比难懂和难维护,可读性很差。
(6)如果有需要,切记使用finally
来释放资源。
(7)如果有需要,请不要忘记在处理异常后做清理工作或者回滚操作。