Python之错误,调试和测试

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模块可以非常容易地记录错误信息:

扫描二维码关注公众号,回复: 2453441 查看本文章
# 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 自定义错误类型

相当于.netthrow

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已有的内置的错误类型(比如ValueErrorTypeError),尽量使用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语句如果不带参数,就会把当前错误原样抛出。此外,在exceptraise一个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才是终极武器

文章参考:

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143191375461417a222c54b7e4d65b258f491c093a515000

猜你喜欢

转载自blog.csdn.net/zengchen73/article/details/76121821