[第三章 Part2]
交互式调试器
- 调试代码最佳时机就是刚刚出错时,%debug命令(发生异常之后马上输入)将会调用“事后”调试器,并直接跳转到引发异常的栈帧(stack frame)。
- 进入调试器后提示符变为 ipdb>
- 输入u(或up)和d(或down)即可切换栈跟踪的各级别。
- 执行%pdb命令可以让IPython出现异常之后自动调用调试器。(pdb是python debug的缩写吧?)
- 设置断点或单步调试: 使用-d选项的%run命令,然后运行之后,必须立即输入s(或step)才能进入脚本。然后输入行号设置断点,使用c(或continue)是脚本运行到断点处。然后输入n(或next)可打不进入断点处,直到前进到发生错误的那一行。
- 注意:调试器命令的优先级高于变量名,在变量前面加上感叹号(!)即可查看其内容。
- 利用IPython作为调试器必须多实践,熟练后效率更高。
- IPython调试器命令
调试器的其他使用场景
- 使用set_trace这个特别的函数(以pdb.set_trace命名),硬编码的比较随意。可以直接将上述两个函数添加到IPython的配置中。
- 第一个set_trace函数可以放到代码中任何希望停下来的地方。(比如发生异常的地方)。
- 按下c(或continue)仍会恢复代码执行。
- 第二个debug函数能够直接在任意函数上使用调试器。例如下面定义f函数之后用debug直接传入参数计算。
结合%run -d执行脚本,在后面再加上 -b 和一个行号可以直接在调试器启动时自动设置断点。
In[2]: %run -d -b2 xxx/xxx/xx.py
测试代码的执行时间:%time和%timeit
使用内置的time模块及其time.clock和time.time函数手工测试代码执行时间是一件令人烦闷的事情,代码如下:
import time
start = time.time()
for i in range(iterations):
# 这里放一些待执行的代码
elapse_per = (time.time() - start) / iterations
%time一次执行一条语句,然后报告总体执行时间。
例如下面是一个有60万字符串的数组,以及两个不同的“选出其中以foo开头的字符串”的方法:
# 一个非常大的字符串数组
strings = ['foo', 'foobar', 'baz', 'qux', 'python', 'Guido Van Rossum'] * 100000
method1 = [x for x in strings if x.startswith('foo')]
method2 = [x for x in string if x[:3] == 'foo']
看上去感觉性能表现应该差不多,对吧?那用%time来确认一下:
如果多次执行%time,结果会变化。为了更精确,可使用%timeit。它会自动多次执行然后产生一个非常精确的平均执行时间。
对于执行时间微妙或者纳秒级的分析,%timeit非常有用,体会下列执行100万次的结果:
基本性能分析:%prun和%run -p
Python的主要性能分析工具是cProfile模块,一般是在命令行上使用。
import numpy as np
from numpy.linalg import eigvals
def run_experiment(niter=100):
k = 100
results = []
for _ in range(niter):
mat = np.random.randn(k,k)
max_eigenvalue = np.abs(eigvals(mat)).max()
results.append(max_eigenvalue)
return results
some_results = run_experiment()
print('Largest one we saw: %s' %np.max(some_results))
注意:书中以Python2.7为例,使用了xrange。python3中取消了xrange,仅有range。
保存为test.py后,保存到命令行根目录后,运行cProfile。
python -m cProfile test.py
执行之后按函数名排序,很难发现哪里最花时间。因此通常用-s标记指定一个排序规则。
python -m cProfile -s cumulative test.py
注意:如果一个函数调用了别的函数,计时器是不会停下来重新计时的。cProfile记录的是各函数调用的起始和结束时间,并依此计算总时间。
IPython提供了%prun和%run -p命令分析性能。其中%prun的格式和cProfile差不多,但它分析的是Python语句而不是整个.py文件。
执行
%run -p -s cumulative test.py
也能达到cProfile命令行命令一样的效果,但无需退出IPython。
逐行分析函数性能
有时候利用%prun得到的信息不足以说明函数执行时间或难以理解。这时候可以使用line_profiler的小型库。其中有魔术函数%lprun,可以对一个或多个函数进行逐行性能分析。可以修改IPython配置以启用这个扩展,代码如下:
# A list of dotted module names of IPython extensions to load.
c.TerminalIPythonApp.extensions = ['line_profiler']
%lprun的通用语法为:
%lprun -f function1 -f function2 statement_to_profile
了解:通常%prun(cProfile)做“宏观的”性能分析,而%lprun(line_profiler)做“微观的”性能分析。
待续......