做为Python初学者,在刚学习Python编程时,常常会看到一些报错信息,在前面咱们没有说起,这章节咱们会专门介绍。html
Python有两种错误很容易辨认:语法错误和异常。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 | 用户代码生成的警告 |
异常便是一个事件,该事件会在程序执行过程当中发生,影响了程序的正常执行。数据库
通常状况下,在Python没法正常处理程序时就会发生一个异常。express
异常是Python对象,表示一个错误。编程
当Python脚本发生异常时咱们须要捕获处理它,不然程序会终止执行。网络
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Can't convert 'int' object to str implicitly
最后一行的错误消息指示发生了什么事。异常有不一样的类型,其类型会做为消息的一部分打印出来:在这个例子中的类型有ZeroDivisionError
、NameError
和 TypeError
.打印出来的异常类型的字符串就是内置的异常的名称。这对于全部内置的异常是正确的,可是对于用户自定义的异常就不必定了(尽管这是很是有用的惯例)。标准异常的名称都是内置的标识符(不是保留的关键字)。eclipse
这一行最后一部分给出了异常的详细信息和引发异常的缘由。ide
错误信息的前面部分以堆栈回溯的形式显示了异常发生的上下文。一般调用栈里会包含源代码的行信息,可是来自标准输入的源码不会显示行信息。函数
内置的异常 列出了内置的异常以及它们的含义。工具
Python 的语法错误或者称之为解析错,是初学者常常碰到的,以下实例
>>> while True print('Hello world') File "<stdin>", line 1, in ? while True print('Hello world') ^ SyntaxError: invalid syntax
这个例子中,函数 print() 被检查到有错误,是它前面缺乏了一个冒号(:)。语法分析器指出了出错的一行,而且在最早找到的错误的位置标记了一个小小的’箭头’。错误是由箭头前面的标记引发的(至少检测到是这样的): 在这个例子中,检测到错误发生在函数 print()
,由于在它以前缺乏一个冒号(':'
)文件名和行号会一并输出,因此若是运行的是一个脚本你就知道去哪里检查错误了。
在程序运行过程当中,总会遇到各类各样的错误。
有的错误是程序编写有问题形成的,好比原本应该输出整数结果输出了字符串,这种错误咱们一般称之为bug,bug是必须修复的。
有的错误是用户输入形成的,好比让用户输入email地址,结果获得一个空字符串,这种错误能够经过检查用户输入来作相应的处理。
还有一类错误是彻底没法在程序运行过程当中预测的,好比写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络忽然断掉了。这类错误也称为异常,在程序中一般是必须处理的,不然,程序会由于各类问题终止并退出。
Python内置了一套异常处理机制,来帮助咱们进行错误处理。
此外,咱们也须要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可让咱们以单步方式执行代码。
最后,编写测试也很重要。有了良好的测试,就能够在程序修改后反复运行,确保程序输出符合咱们编写的测试。
open()
,成功时返回文件描述符(就是一个整数),出错时返回
-1
。
用错误码来表示是否出错十分不便,由于函数自己应该返回的正常结果和错误码混在一块儿,形成调用者必须用大量的代码来判断是否出错:
def foo(): r = some_function() if r==(-1): return (-1) # do something return r def bar(): r = foo() if r==(-1): print('Error') else: pass
一旦出错,还要一级一级上报,直到某个函数能够处理该错误(好比,给用户输出一个错误信息)。
因此高级语言一般都内置了一套try...except...finally...
的错误处理机制,Python也不例外。
让咱们用一个例子来看看try
的机制:
try: print('try...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e: print('except:', e) finally: print('finally...') print('END')
当咱们认为某些代码可能会出错时,就能够用try
来运行这段代码,若是执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except
语句块,执行完except
后,若是有finally
语句块,则执行finally
语句块,至此,执行完毕。
上面的代码在计算10 / 0
时会产生一个除法运算错误:
try... except: division by zero finally... END
从输出能够看到,当错误发生时,后续语句print('result:', r)
不会被执行,except
因为捕获到ZeroDivisionError
,所以被执行。最后,finally
语句被执行。而后,程序继续按照流程往下走。
若是把除数0
改为2
,则执行结果以下:
try... result: 5 finally... END
因为没有错误发生,因此except
语句块不会被执行,可是finally
若是有,则必定会被执行(能够没有finally
语句)。
你还能够猜想,错误应该有不少种类,若是发生了不一样类型的错误,应该由不一样的except
语句块处理。没错,能够有多个except
来捕获不一样类型的错误:
try: print('try...') r = 10 / int('a') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) finally: print('finally...') print('END')
int()
函数可能会抛出ValueError
,因此咱们用一个except
捕获ValueError
,用另外一个except
捕获ZeroDivisionError
。
此外,若是没有错误发生,能够在except
语句块后面加一个else
,当没有错误发生时,会自动执行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')
Python的错误其实也是class,全部的错误类型都继承自BaseException
,因此在使用except
时须要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。好比:
try: foo() except ValueError as e: print('ValueError') except UnicodeError as e: print('UnicodeError')
第二个except
永远也捕获不到UnicodeError
,由于UnicodeError
是ValueError
的子类,若是有,也被第一个except
给捕获了。
Python全部的错误都是从BaseException
类派生的,常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
使用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
的麻烦。
捕捉异常可使用try/except语句。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
若是你不想在异常发生时结束你的程序,只需在try里捕获它。
语法:
如下为简单的try....except...else的语法:
try: <语句> #运行别的代码 except <名字>: <语句> #若是在try部份引起了'name'异常 except <名字>,<数据>: <语句> #若是引起了'name'异常,得到附加的数据 else: <语句> #若是没有异常发生
try
语句按如下方式工做。
try
和 except
关键字之间的语句)。try
语句执行完毕。except
关键字后面的异常名匹配, 则执行 except 子句,而后继续执行 try
语句以后的代码。try
语句;若是没有找处处理这个异常的代码,它就成为一个 未处理异常 ,程序会终止运行并显示一条如上所示的信息。try
语句可能有多个子句,以指定不一样的异常处理程序。不过至多只有一个处理程序将被执行。处理程序只处理发生在相应 try 子句中的异常,不会处理同一个 try
子句的其余处理程序中发生的异常。一个 except 子句能够用带括号的元组列出多个异常的名字,例如:
... except (RuntimeError, TypeError, NameError): ... pass
最后一个 except 子句能够省略异常名称,以看成通配符使用。使用这种方式要特别当心,由于它会隐藏一个真实的程序错误!它还能够用来打印一条错误消息,而后从新引起异常 (让调用者也去处理这个异常):
import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print("OS error: {0}".format(err)) except ValueError: print("Could not convert data to an integer.") except: print("Unexpected error:", sys.exc_info()[0]) raise
try
...except
语句有一个可选的 else 子句 ,其出现时,必须放在全部 except 子句的后面。若是须要在 try 语句没有抛出异常时执行一些代码,可使用这个子句。例如:
for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print('cannot open', arg) else: print(arg, 'has', len(f.readlines()), 'lines') f.close()
使用 else
子句比把额外的代码放在 try
子句中要好,由于它能够避免意外捕获不是由 try
... except
语句保护的代码所引起的异常。
当异常发生时,它可能带有相关数据,也称为异常的参数。参数的有无和类型取决于异常的类型。
except 子句能够在异常名以后指定一个变量。这个变量将绑定于一个异常实例,同时异常的参数将存放在 实例的args
中。为方便起见,异常实例定义了 __str__()
,所以异常的参数能够直接打印而没必要引用 .args
。也能够在引起异常以前先实例化一个异常,而后向它添加任何想要的属性。
>>> try: ... raise Exception('spam', 'eggs') ... except Exception as inst: ... print(type(inst)) # the exception instance ... print(inst.args) # arguments stored in .args ... print(inst) # __str__ allows args to be printed directly, ... # but may be overridden in exception subclasses ... x, y = inst.args # unpack args ... print('x =', x) ... print('y =', y) ... <class 'Exception'> ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggs
对于未处理的异常,若是它含有参数,那么参数会做为异常信息的最后一部分打印出来。
异常处理程序不只处理直接发生在 try 子句中的异常,并且还处理 try 子句中调用的函数(甚至间接调用的函数)引起的异常。例如:
>>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError as err: ... print('Handling run-time error:', err) ... Handling run-time error: int division or modulo by zero
能够经过编程来选择处理部分异常。看一下下面的例子,它会一直要求用户输入直到输入一个合法的整数为止,但容许用户中断这个程序(使用 Control-C
或系统支持的任何方法);注意用户产生的中断引起的是 KeyboardInterrupt
异常。
>>> while True: ... try: ... x = int(input("Please enter a number: ")) ... break ... except ValueError: ... print("Oops! That was no valid number. Try again...") ...
Python 使用 raise 语句抛出一个指定的异常。例如:
>>> raise NameError('HiThere') Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: HiThere
raise 惟一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。
若是你只想知道这是否抛出了一个异常,并不想去处理它,那么一个简单的 raise 语句就能够再次把它抛出。
>>> try: raise NameError('HiThere') except NameError: print('An exception flew by!') raise An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in ? NameError: HiThere
若是错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,而后程序退出。来看看err.py
:
# err.py: def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main()
执行,结果以下:
$ python3 err.py Traceback (most recent call last): File "err.py", line 11, in <module> main() File "err.py", line 9, in main bar('0') File "err.py", line 6, in bar return foo(s) * 2 File "err.py", line 3, in foo return 10 / int(s) ZeroDivisionError: division by zero
出错并不可怕,可怕的是不知道哪里出错了。解读错误信息是定位错误的关键。咱们从上往下能够看到整个错误的调用函数链:
错误信息第1行:
Traceback (most recent call last):
告诉咱们这是错误的跟踪信息。
第2~3行:
File "err.py", line 11, in <module> main()
调用main()
出错了,在代码文件err.py
的第11行代码,但缘由是第9行:
File "err.py", line 9, in main bar('0')
调用bar('0')
出错了,在代码文件err.py
的第9行代码,但缘由是第6行:
File "err.py", line 6, in bar return foo(s) * 2
缘由是return foo(s) * 2
这个语句出错了,但这还不是最终缘由,继续往下看:
File "err.py", line 3, in foo return 10 / int(s)
缘由是return 10 / int(s)
这个语句出错了,这是错误产生的源头,由于下面打印了:
ZeroDivisionError: integer division or modulo by zero
根据错误类型ZeroDivisionError
,咱们判断,int(s)
自己并无出错,可是int(s)
返回0
,在计算10 / 0
时出错,至此,找到错误源头。
若是不捕获错误,天然可让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')
一样是出错,但程序打印完错误信息后会继续执行,并正常退出:
$ python3 err_logging.py ERROR:root:division by zero Traceback (most recent call last): File "err_logging.py", line 13, in main bar('0') File "err_logging.py", line 9, in bar return foo(s) * 2 File "err_logging.py", line 6, in foo return 10 / int(s) ZeroDivisionError: division by zero END
经过配置,logging
还能够把错误记录到日志文件里,方便过后排查。
由于错误是class,捕获一个错误就是捕获到该class的一个实例。所以,错误并非凭空产生的,而是有意建立并抛出的。Python的内置函数会抛出不少类型的错误,咱们本身编写的函数也能够抛出错误。
若是要抛出错误,首先根据须要,能够定义一个错误的class,选择好继承关系,而后,用raise
语句抛出一个错误的实例:
# err_raise.py class FooError(ValueError): pass def foo(s): n = int(s) if n==0: raise FooError('invalid value: %s' % s) return 10 / n foo('0')
执行,能够最后跟踪到咱们本身定义的错误:
$ python3 err_raise.py Traceback (most recent call last): File "err_throw.py", line 11, in <module> foo('0') File "err_throw.py", line 8, in foo raise FooError('invalid value: %s' % s) __main__.FooError: invalid value: 0
只有在必要的时候才定义咱们本身的错误类型。若是能够选择Python已有的内置的错误类型(好比ValueError
,TypeError
),尽可能使用Python内置的错误类型。
最后,咱们来看另外一种错误处理的方式:
# err_reraise.py def foo(s): n = int(s) if n==0: raise ValueError('invalid value: %s' % s) return 10 / n def bar(): try: foo('0') except ValueError as e: print('ValueError!') raise bar()
在bar()
函数中,咱们明明已经捕获了错误,可是,打印一个ValueError!
后,又把错误经过raise
语句抛出去了,这不有病么?
其实这种错误处理方式不但没病,并且至关常见。捕获错误目的只是记录一下,便于后续追踪。可是,因为当前函数不知道应该怎么处理该错误,因此,最恰当的方式是继续往上抛,让顶层调用者去处理。比如一个员工处理不了一个问题时,就把问题抛给他的老板,若是他的老板也处理不了,就一直往上抛,最终会抛给CEO去处理。
raise
语句若是不带参数,就会把当前错误原样抛出。此外,在except
中raise
一个Error,还能够把一种类型的错误转化成另外一种类型:
try: 10 / 0 except ZeroDivisionError: raise ValueError('input error!')
只要是合理的转换逻辑就能够,可是,决不该该把一个IOError
转换成绝不相干的ValueError
。
Python内置的try...except...finally
用来处理错误十分方便。出错时,会分析错误信息并定位错误发生的代码位置才是最关键的。
程序也能够主动抛出错误,让调用者来处理相应的错误。可是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的缘由。
程序能够经过建立新的异常类来命名本身的异常(Python 类的更多内容请参见 类 )。异常一般应该继承 Exception
类,直接继承或者间接继承均可以。
异常类能够像其余类同样作任何事情,可是一般都会比较简单,只提供一些属性以容许异常处理程序获取错误相关的信息。建立一个可以引起几种不一样错误的模块时,一个一般的作法是为该模块定义的异常建立一个基类,而后基于这个基类为不一样的错误状况建立特定的子类:
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
无论有没有发生异常,在离开 try
语句以前老是会执行 finally 子句。当 try
子句中发生了一个异常,而且没有 except
字句处理(或者异常发生在 except
或 else
子句中),在执行完 finally
子句后将从新引起这个异常。try
语句因为 break
、contine
或return
语句离开时,一样会执行finally
子句。下面是一个更复杂些的例子:
>>> def divide(x, y): ... try: ... result = x / y ... except ZeroDivisionError: ... print("division by zero!") ... else: ... print("result is", result) ... finally: ... print("executing finally clause") ... >>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
正如您所看到的,在任何状况下都会执行 finally
子句。由两个字符串相除引起的 TypeError
异常没有被 except
子句处理,所以在执行 finally
子句后被从新引起。
在真实的应用程序中, finally
子句用于释放外部资源(例如文件或网络链接),无论资源的使用是否成功。
大多数异常的名字都以"Error"结尾,相似于标准异常的命名。
不少标准模块中都定义了本身的异常来报告在它们所定义的函数中可能发生的错误。类 这一章给出了类的详细信息。
>>> class MyError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) >>> try: raise MyError(2*2) except MyError as e: print('My exception occurred, value:', e.value) My exception occurred, value: 4 >>> raise MyError('oops!') Traceback (most recent call last): File "<stdin>", line 1, in ? __main__.MyError: 'oops!'
在这个例子中,类 Exception 默认的 __init__() 被覆盖。
try 语句还有另一个可选的子句,它定义了不管在任何状况下都会执行的清理行为。 例如:
>>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') ... Goodbye, world! KeyboardInterrupt Traceback (most recent call last): File "<stdin>", line 2, in ?
以上例子无论try子句里面有没有发生异常,finally子句都会执行。
若是一个异常在 try 子句里(或者在 except 和 else 子句里)被抛出,而又没有任何的 except 把它截住,那么这个异常会在 finally 子句执行后再次被抛出。
下面是一个更加复杂的例子(在同一个 try 语句里包含 except 和 finally 子句):
>>> def divide(x, y): try: result = x / y except ZeroDivisionError: print("division by zero!") else: print("result is", result) finally: print("executing finally clause") >>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
一些对象定义了标准的清理行为,不管系统是否成功的使用了它,一旦不须要它了,那么这个标准的清理行为就会执行。
这面这个例子展现了尝试打开一个文件,而后把内容打印到屏幕上:
for line in open("myfile.txt"): print(line, end="")
以上这段代码的问题是,当执行完毕后,文件会保持打开状态,并无被关闭。
关键词 with 语句就能够保证诸如文件之类的对象在使用完以后必定会正确的执行他的清理方法:
with open("myfile.txt") as f: for line in f: print(line, end="")
执行该语句后,文件 f 将始终被关闭,即便在处理某一行时遇到了问题。提供预约义的清理行为的对象,和文件同样,会在它们的文档里说明。
第一种方法简单直接粗暴有效,就是用print()
把可能有问题的变量打印出来看看:
def foo(s): n = int(s) print('>>> n = %d' % n) return 10 / n def main(): foo('0') main()
执行后在输出中查找打印的变量值:
$ python3 err.py >>> n = 0 Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero
用print()
最大的坏处是未来还得删掉它,想一想程序里处处都是print()
,运行结果也会包含不少垃圾信息。因此,咱们又有第二种方法。
凡是用print()
来辅助查看的地方,均可以用断言(assert)来替代:
def foo(s): n = int(s) assert n != 0, 'n is zero!' return 10 / n def main(): foo('0')
assert
的意思是,表达式n != 0
应该是True
,不然,根据程序运行的逻辑,后面的代码确定会出错。
若是断言失败,assert
语句自己就会抛出AssertionError
:
$ python3 err.py Traceback (most recent call last): ... AssertionError: n is zero!
程序中若是处处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时能够用-O
参数来关闭assert
:
$ python3 -O err.py Traceback (most recent call last): ... ZeroDivisionError: division by zero
关闭后,你能够把全部的assert
语句当成pass
来看。
把print()
替换为logging
是第3种方式,和assert
比,logging
不会抛出错误,并且能够输出到文件:
import logging s = '0' n = int(s) logging.info('n = %d' % n) print(10 / n)
logging.info()
就能够输出一段文本。运行,发现除了ZeroDivisionError
,没有任何信息。怎么回事?
别急,在import logging
以后添加一行配置再试试:
import logging logging.basicConfig(level=logging.INFO)
看到输出了:
$ python3 err.py INFO:root:n = 0 Traceback (most recent call last): File "err.py", line 8, in <module> print(10 / n) ZeroDivisionError: division by zero
这就是logging
的好处,它容许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当咱们指定level=INFO
时,logging.debug
就不起做用了。同理,指定level=WARNING
后,debug
和info
就不起做用了。这样一来,你能够放心地输出不一样级别的信息,也不用删除,最后统一控制输出哪一个级别的信息。
logging
的另外一个好处是经过简单的配置,一条语句能够同时输出到不一样的地方,好比console和文件。
第4种方式是启动Python的调试器pdb,让程序以单步方式运行,能够随时查看运行状态。咱们先准备好程序:
# err.py s = '0' n = int(s) print(10 / n)
而后启动:
$ python3 -m pdb err.py > /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>() -> s = '0'
以参数-m pdb
启动后,pdb定位到下一步要执行的代码-> s = '0'
。输入命令l
来查看代码:
(Pdb) l 1 # err.py 2 -> s = '0' 3 n = int(s) 4 print(10 / n)
输入命令n
能够单步执行代码:
(Pdb) n > /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>() -> n = int(s) (Pdb) n > /Users/michael/Github/learn-python3/samples/debug/err.py(4)<module>() -> print(10 / n)
任什么时候候均可以输入命令p 变量名
来查看变量:
(Pdb) p s '0' (Pdb) p n 0
输入命令q
结束调试,退出程序:
(Pdb) q
这种经过pdb在命令行调试的方法理论上是万能的,但实在是太麻烦了,若是有一千行代码,要运行到第999行得敲多少命令啊。还好,咱们还有另外一种调试方法。
这个方法也是用pdb,可是不须要单步执行,咱们只须要import pdb
,而后,在可能出错的地方放一个pdb.set_trace()
,就能够设置一个断点:
# err.py import pdb s = '0' n = int(s) pdb.set_trace() # 运行到这里会自动暂停 print(10 / n)
运行代码,程序会自动在pdb.set_trace()
暂停并进入pdb调试环境,能够用命令p
查看变量,或者用命令c
继续运行:
$ python3 err.py > /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>() -> print(10 / n) (Pdb) p n 0 (Pdb) c Traceback (most recent call last): File "err.py", line 7, in <module> print(10 / n) ZeroDivisionError: division by zero
这个方式比直接启动pdb单步调试效率要高不少,但也高不到哪去。
若是要比较爽地设置断点、单步执行,就须要一个支持调试功能的IDE。目前比较好的Python IDE有PyCharm:
http://www.jetbrains.com/pycharm/
另外,Eclipse加上pydev插件也能够调试Python程序。
写程序最痛苦的事情莫过于调试,程序每每会以你意想不到的流程来运行,你期待执行的语句其实根本没有执行,这时候,就须要调试了。
虽然用IDE调试起来比较方便,可是最后你会发现,logging才是终极武器。
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工做。
好比对函数abs()
,咱们能够编写出如下几个测试用例:
输入正数,好比1
、1.2
、0.99
,期待返回值与输入相同;
输入负数,好比-1
、-1.2
、-0.99
,期待返回值与输入相反;
输入0
,期待返回0
;
输入非数值类型,好比None
、[]
、{}
,期待抛出TypeError
。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
若是单元测试经过,说明咱们测试的这个函数可以正常工做。若是单元测试不经过,要么函数有bug,要么测试条件输入不正确,总之,须要修复使单元测试可以经过。
单元测试经过后有什么意义呢?若是咱们对abs()
函数代码作了修改,只须要再跑一遍单元测试,若是经过,说明咱们的修改不会对abs()
函数原有的行为形成影响,若是测试不经过,说明咱们的修改与原有行为不一致,要么修改代码,要么修改测试。
这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合咱们设计的测试用例。在未来修改的时候,能够极大程度地保证该模块行为仍然是正确的。
咱们来编写一个Dict
类,这个类的行为和dict
一致,可是能够经过属性来访问,用起来就像下面这样:
>>> d = Dict(a=1, b=2) >>> d['a'] 1 >>> d.a 1
mydict.py
代码以下:
class Dict(dict): def __init__(self, **kw): super().__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value
为了编写单元测试,咱们须要引入Python自带的unittest
模块,编写mydict_test.py
以下:
import unittest from mydict import Dict class TestDict(unittest.TestCase): def test_init(self): d = Dict(a=1, b='test') self.assertEqual(d.a, 1) self.assertEqual(d.b, 'test') self.assertTrue(isinstance(d, dict)) def test_key(self): d = Dict() d['key'] = 'value' self.assertEqual(d.key, 'value') def test_attr(self): d = Dict() d.key = 'value' self.assertTrue('key' in d) self.assertEqual(d['key'], 'value') def test_keyerror(self): d = Dict() with self.assertRaises(KeyError): value = d['empty'] def test_attrerror(self): d = Dict() with self.assertRaises(AttributeError): value = d.empty
编写单元测试时,咱们须要编写一个测试类,从unittest.TestCase
继承。
以test
开头的方法就是测试方法,不以test
开头的方法不被认为是测试方法,测试的时候不会被执行。
对每一类测试都须要编写一个test_xxx()
方法。因为unittest.TestCase
提供了不少内置的条件判断,咱们只须要调用这些方法就能够断言输出是不是咱们所指望的。最经常使用的断言就是
assertEqual(): self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等
另外一种重要的断言就是期待抛出指定类型的Error,好比经过d['empty']
访问不存在的key时,断言会抛出KeyError
:
with self.assertRaises(KeyError): value = d['empty']
而经过d.empty
访问不存在的key时,咱们期待抛出AttributeError
:
with self.assertRaises(AttributeError): value = d.empty
一旦编写好单元测试,咱们就能够运行单元测试。最简单的运行方式是在mydict_test.py
的最后加上两行代码:
if __name__ == '__main__': unittest.main()
这样就能够把mydict_test.py
当作正常的python脚本运行:
$ python3 mydict_test.py
另外一种方法是在命令行经过参数-m unittest
直接运行单元测试:
$ python3 -m unittest mydict_test ..... ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK
这是推荐的作法,由于这样能够一次批量运行不少单元测试,而且,有不少工具能够自动来运行这些单元测试。
能够在单元测试中编写两个特殊的setUp()
和tearDown()
方法。这两个方法会分别在每调用一个测试方法的先后分别被执行。
setUp()
和tearDown()
方法有什么用呢?设想你的测试须要启动一个数据库,这时,就能够在setUp()
方法中链接数据库,在tearDown()
方法中关闭数据库,这样,没必要在每一个测试方法中重复相同的代码:
class TestDict(unittest.TestCase): def setUp(self): print('setUp...') def tearDown(self): print('tearDown...')
能够再次运行测试看看每一个测试方法调用先后是否会打印出setUp...
和tearDown...
。
单元测试能够有效地测试某个程序模块的行为,是将来重构代码的信心保证。
单元测试的测试用例要覆盖经常使用的输入组合、边界条件和异常。
单元测试代码要很是简单,若是测试代码太复杂,那么测试代码自己就可能有bug。
单元测试经过了并不意味着程序就没有bug了,可是不经过程序确定有bug。
>>> import re >>> m = re.search('(?<=abc)def', 'abcdef') >>> m.group(0) 'def'
能够把这些示例代码在Python的交互式环境下输入并执行,结果与文档中的示例代码显示的一致。
这些代码与其余说明能够写在注释中,而后,由一些工具来自动生成文档。既然这些代码自己就能够粘贴出来直接运行,那么,可不能够自动执行写在注释中的这些代码呢?
答案是确定的。
当咱们编写注释时,若是写上这样的注释:
def abs(n): ''' Function to get absolute value of number. Example: >>> abs(1) 1 >>> abs(-1) 1 >>> abs(0) 0 ''' return n if n >= 0 else (-n)
无疑更明确地告诉函数的调用者该函数的指望输入和输出。
而且,Python内置的“文档测试”(doctest)模块能够直接提取注释中的代码并执行测试。
doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,能够用...
表示中间一大段烦人的输出。
让咱们用doctest来测试上次编写的Dict
类:
# mydict2.py class Dict(dict): ''' Simple dict but also support access as x.y style. >>> d1 = Dict() >>> d1['x'] = 100 >>> d1.x 100 >>> d1.y = 200 >>> d1['y'] 200 >>> d2 = Dict(a=1, b=2, c='3') >>> d2.c '3' >>> d2['empty'] Traceback (most recent call last): ... KeyError: 'empty' >>> d2.empty Traceback (most recent call last): ... AttributeError: 'Dict' object has no attribute 'empty' ''' def __init__(self, **kw): super(Dict, self).__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value if __name__=='__main__': import doctest doctest.testmod()
运行python3 mydict2.py
:
$ python3 mydict2.py
什么输出也没有。这说明咱们编写的doctest运行都是正确的。若是程序有问题,好比把__getattr__()
方法注释掉,再运行就会报错:
$ python3 mydict2.py ********************************************************************** File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 10, in __main__.Dict Failed example: d1.x Exception raised: Traceback (most recent call last): ... AttributeError: 'Dict' object has no attribute 'x' ********************************************************************** File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 16, in __main__.Dict Failed example: d2.c Exception raised: Traceback (most recent call last): ... AttributeError: 'Dict' object has no attribute 'c' ********************************************************************** 1 items had failures: 2 of 9 in __main__.Dict ***Test Failed*** 2 failures.
注意到最后3行代码。当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。因此,没必要担忧doctest会在非测试环境下执行。