1. 错误处理
1.1 try …except…
1.1.1 try …except…
try:
print ("try...")
r = 10 / 0
print ("result:",r)
except ZeroDivisionError as e:
print ("except:",e)
finally:
print ("finally...")
print ("END")
# try...
# except: division by zero
# finally...
# END
1.1.2 except…else…
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')
else部分
- 当没有异常时执行
- 当异常种类不在except中时执行
1.1.3 异常种类
Python的错误其实也是class,所有的错误类型都继承自BaseException
,所以在使用except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
异常继承关系如下:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
1.1.4 跨层调用
使用try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()
调用foo()
,foo()
调用bar()
,结果bar()
出错了,这时,只要main()
捕获到了,就可以处理:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally
的麻烦。
1.2 调用堆栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来看看err.py
:
# err.py:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
执行,结果如下:
1.3 记录错误 - logging
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging
模块可以非常容易地记录错误信息:
# err_logging.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
通过配置,logging
还可以把错误记录到日志文件里,方便事后排查。
1.4 抛出错误 - raise
1.4.1 自定义错误类型
相当于.net
的throw
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError
,TypeError
),尽量使用Python内置的错误类型。
1.4.2 抛出异常
如果一个函数中有异常而没有被处理,会自动一直上抛,最后被Python解释器捕获,如1.2例子,所以,在本方法中,或者任意一层调用者中进行异常处理都可以
如果在一个方法抛出异常,而在中间调用层没有处理,一直到顶层调用层处理,就是被顶层调用层捕获
def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n
def abc(s):
foo(s)
def bar():
try:
abc('0')
except ValueError as e:
print('ValueError!')
raise
bar()
1.4.3 转换错误类型
raise
语句如果不带参数,就会把当前错误原样抛出。此外,在except
中raise
一个Error,还可以把一种类型的错误转化成另一种类型:
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
只要是合理的转换逻辑就可以,但是,决不应该把一个IOError
转换成毫不相干的ValueError
。
1.5 淹没异常
def foo(s):
try:
return 10 / int(s)
except:
print ("test")
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
print('END')
理论上来讲这个程序是把异常淹没了,但是结果是
test
Traceback (most recent call last):
File "C:/Users/disheng.zeng/Develop/Python/Scrapy/purePython1/main.py", line 28, in <module>
main()
File "C:/Users/disheng.zeng/Develop/Python/Scrapy/purePython1/main.py", line 18, in main
bar('0')
File "C:/Users/disheng.zeng/Develop/Python/Scrapy/purePython1/main.py", line 15, in bar
return foo(s) * 2
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
原因就在于异常处理中没有return
def foo(s):
try:
return 10 / int(s)
except:
print ("test")
return 1
结果
test
END
1.6 try…except…和if…else…
if else, 必须要条件 - 意味着你需要知道你执行代码正确输出或返回是什么,错误又是哪些,你的逻辑复杂了以后,往往你不能枚举所有的可能性,更不要说那些不可预知的错误了:)
raise-except如果在本层不能被捕获会被继续向上抛,if-else能做到吗?
除了楼上几位说的,还有一个原因是如果一段代码不管是否发生异常都必须要执行的话,使用if…else会有大量冗余代码,不够优美
2. 调试
2.1 print
第一种方法简单直接粗暴有效,就是用print()
把可能有问题的变量打印出来看看:
2.2 断言 assert
Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息
断言本身就是很好的注释,胜过你直接写注释:
# when we reach here, we know that n > 2
你可以通过添加断言来确保它:
assert n > 2
断言也是一种防御型编程。你不是让你的代码防御现在的错误,而是防止在代码修改后引发的错误。
断言的解释以及应用场景
http://blog.jobbole.com/76285/
凡是用print()
来辅助查看的地方,都可以用断言(assert)来替代:
断言有两部分组成
- 逗号前表示断言判断内容
- 逗号后表示如果判断失败,抛出异常内容
如果断言失败,assert
语句本身就会抛出AssertionError
:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 1
def main():
foo('0')
main()
print("test")
程序中如果到处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时可以用-O
参数来关闭assert
:
$ python3 -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
关闭后,你可以把所有的assert
语句当成pass
来看。
2.3 logging
2.4 pdb
2.5 IDE
如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的IDE。目前比较好的Python IDE有PyCharm:
http://www.jetbrains.com/pycharm/
另外,Eclipse加上pydev插件也可以调试Python程序。
2.5.1 pycharm中如何调试
http://blog.csdn.net/icycolawater/article/details/51239525
小结
写程序最痛苦的事情莫过于调试,程序往往会以你意想不到的流程来运行,你期待执行的语句其实根本没有执行,这时候,就需要调试了。
虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。
文章参考: