Skip to content

Latest commit

 

History

History
279 lines (189 loc) · 13.5 KB

File metadata and controls

279 lines (189 loc) · 13.5 KB

二、调试和分析 Python 脚本

调试和评测在 Python 开发中起着重要作用。调试器帮助程序员分析完整的代码。调试器设置断点,而探查器运行我们的代码,并向我们提供执行时间的详细信息。分析器将识别程序中的瓶颈。在本章中,我们将了解用于计时 Python 代码执行的pdbPython 调试器、cProfile模块和timeit模块。

在本章中,您将了解以下内容:

  • Python 调试技术
  • 错误处理(异常处理)
  • 调试器工具
  • 调试基本程序崩溃
  • 分析和计时程序
  • 使程序运行更快

什么是调试?

调试是一个解决代码中出现的问题并阻止软件正常运行的过程。在 Python 中,调试非常简单。Python 调试器设置条件断点,并一次调试一行源代码。我们将使用 Python 标准库中的pdb模块调试 Python 脚本。

Python 调试技术

为了更好地调试 Python 程序,可以使用各种技术。我们将研究 Python 调试的四种技术:

  • print()语句:这是知道到底发生了什么的最简单方法,这样你就可以检查执行了什么。
  • logging:这就像一个print语句,但有更多的上下文信息,所以你可以完全理解它。
  • **pdb**调试器:这是一种常用的调试技术。使用pdb的优点是,您可以从命令行、解释器和程序中使用pdb
  • IDE 调试器:IDE 有一个集成的调试器。它允许开发人员执行他们的代码,然后开发人员可以在程序执行时进行检查。

错误处理(异常处理)

在本节中,我们将学习 Python 如何处理异常。但首先,什么是例外?异常是程序执行期间发生的错误。每当发生任何错误时,Python 都会生成一个异常,该异常将使用try…except块进行处理。某些异常无法由程序处理,因此会导致错误消息。现在,我们将看到一些异常示例。

在您的终端中,启动python3交互控制台,我们将看到一些异常示例:

student@ubuntu:~$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> 50 / 0 Traceback (most recent call last):
 File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero >>> >>> 6 + abc*5 Traceback (most recent call last):
 File "<stdin>", line 1, in <module> NameError: name 'abc' is not defined >>> >>> 'abc' + 2 Traceback (most recent call last):
 File "<stdin>", line 1, in <module> TypeError: Can't convert 'int' object to str implicitly >>> >>> import abcd Traceback (most recent call last):
 File "<stdin>", line 1, in <module> ImportError: No module named 'abcd' >>> 

这些是一些例外的例子。现在,我们将了解如何处理异常。

每当 Python 程序中出现错误时,就会引发异常。我们还可以使用raise关键字强制引发异常。

现在我们将看到一个处理异常的try…except块。在try块中,我们将编写一个可能生成异常的代码。在except块中,我们将为该异常编写一个解决方案。

try…except的语法如下:

try:
 statement(s)
except:
 statement(s)

try块可以有多个 except 语句。我们也可以通过在except关键字后输入异常名称来处理特定的异常。处理特定异常的语法如下所示:

try:
 statement(s)
except exception_name:
 statement(s)

我们将创建一个exception_example.py脚本来捕捉ZeroDivisionError**。**在脚本中编写以下代码:

a = 35 b = 57 try:
 c = a + b print("The value of c is: ", c) d = b / 0 print("The value of d is: ", d)except:
 print("Division by zero is not possible")print("Out of try...except block")

按如下方式运行脚本,您将获得以下输出:

student@ubuntu:~$ python3 exception_example.py The value of c is:  92 Division by zero is not possible Out of try...except block

调试器工具

Python 支持许多调试工具:

  • winpdb
  • pydev
  • pydb
  • pdb
  • gdb
  • pyDebug

在本节中,我们将学习pdbPython 调试器。pdb模块是 Python 标准库的一部分,可以随时使用。

pdb 调试器

pdb模块用于调试 Python 程序。Python 程序使用pdb交互式源代码调试器来调试程序。pdb设置断点并检查堆栈帧,并列出源代码。

现在我们将学习如何使用pdb调试器。使用此调试器有三种方法:

  • 在口译员内
  • 从命令行
  • 在 Python 脚本中

我们将创建一个pdb_example.py脚本,并在该脚本中添加以下内容:

class Student:
 def __init__(self, std): self.count = std            def print_std(self):
 for i in range(self.count): print(i) return if __name__ == '__main__':
 Student(5).print_std()

以这个脚本为例学习 Python 调试,我们将详细了解如何启动调试器。

在口译员内

要从 Python 交互控制台启动调试器,我们使用run()runeval()

启动您的python3交互式控制台。运行以下命令以启动控制台:

 $ python3

导入我们的pdb_example脚本名称和pdb模块。现在,我们将使用run()并将字符串表达式作为参数传递给run(),该参数将由 Python 解释器本身进行计算:

student@ubuntu:~$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> import pdb_example >>> import pdb >>> pdb.run('pdb_example.Student(5).print_std()') > <string>(1)<module>() (Pdb)

若要继续调试,请在(Pdb提示后输入continue,然后按回车。如果您想知道我们可以使用的选项,则在(Pdb提示后,按两次Tab键。

现在,输入continue后,我们将得到如下输出:

student@ubuntu:~$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> import pdb_example >>> import pdb >>> pdb.run('pdb_example.Student(5).print_std()') > <string>(1)<module>() (Pdb) continue 0 1 2 3 4 >>> 

从命令行

运行调试器最简单、最直接的方法是从命令行。我们的程序将作为调试器的输入。可以从命令行使用调试器,如下所示:

$ python3 -m pdb pdb_example.py

从命令行运行调试器时,将加载源代码,并在找到的第一行停止执行。输入continue继续调试。以下是输出:

student@ubuntu:~$ python3 -m pdb pdb_example.py > /home/student/pdb_example.py(1)<module>() -> class Student: (Pdb) continue 0 1 2 3 4 The program finished and will be restarted > /home/student/pdb_example.py(1)<module>() -> class Student: (Pdb)

在 Python 脚本中

前两种技术将在 Python 程序开始时启动调试器。但第三种技术最适合于长时间运行的流程。要在脚本中启动调试器,请使用set_trace()

现在,修改您的pdb_example.py文件如下:

import pdb class Student:
 def __init__(self, std): self.count = std            def print_std(self):
 for i in range(self.count): pdb.set_trace() print(i) returnif __name__ == '__main__':
 Student(5).print_std()

现在,按如下方式运行程序:

student@ubuntu:~$ python3 pdb_example.py > /home/student/pdb_example.py(10)print_std() -> print(i) (Pdb) continue 0 > /home/student/pdb_example.py(9)print_std() -> pdb.set_trace() (Pdb)

set_trace()是一个 Python 函数,因此您可以在程序中的任何时候调用它。

下面是启动调试器的三种方法。

调试基本程序崩溃

在本节中,我们将看到跟踪模块。跟踪模块有助于跟踪程序执行。因此,每当 Python 程序崩溃时,我们都可以理解它在哪里崩溃。我们可以通过将跟踪模块导入脚本以及从命令行导入来使用它。

现在,我们将创建一个名为trace_example.py的脚本,并在脚本中写入以下内容:

class Student:
 def __init__(self, std): self.count = std            def go(self):
 for i in range(self.count): print(i) return if __name__ == '__main__':
 Student(5).go()

输出结果如下:

student@ubuntu:~$ python3 -m trace --trace trace_example.py
 --- modulename: trace_example, funcname: <module> trace_example.py(1): class Student:
 --- modulename: trace_example, funcname: Student trace_example.py(1): class Student: trace_example.py(2):   def __init__(self, std): trace_example.py(5):   def go(self): trace_example.py(10): if __name__ == '__main__': trace_example.py(11):             Student(5).go()
 --- modulename: trace_example, funcname: init trace_example.py(3):               self.count = std
 --- modulename: trace_example, funcname: go trace_example.py(6):               for i in range(self.count): trace_example.py(7):                           print(i) 0 trace_example.py(6):               for i in range(self.count): trace_example.py(7):                           print(i) 1 trace_example.py(6):               for i in range(self.count): trace_example.py(7):                           print(i) 2 trace_example.py(6):               for i in range(self.count): trace_example.py(7):                           print(i) 3 trace_example.py(6):               for i in range(self.count): trace_example.py(7):                           print(i) 4

因此,通过在命令行中使用trace --trace,开发人员可以逐行跟踪程序。因此,每当程序崩溃时,开发人员都会知道它崩溃的实例。

分析和计时程序

评测 Python 程序意味着测量程序的执行时间。它测量每个功能所花费的时间。Python 的cProfile模块用于分析 Python 程序。

cProfile 模块

如前所述,评测意味着测量程序的执行时间。我们将使用cProfilePython 模块分析程序。

现在,我们将编写一个cprof_example.py脚本,并在其中编写以下代码:

mul_value = 0 def mul_numbers( num1, num2 ):
 mul_value = num1 * num2; print ("Local Value: ", mul_value) return mul_value mul_numbers( 58, 77 ) print ("Global Value: ", mul_value)

运行该程序,您将看到如下输出:

student@ubuntu:~$ python3 -m cProfile cprof_example.py Local Value:  4466 Global Value:  0
 6 function calls in 0.000 seconds Ordered by: standard name   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 1    0.000    0.000    0.000    0.000 cprof_example.py:1(<module>) 1    0.000    0.000    0.000    0.000 cprof_example.py:2(mul_numbers) 1    0.000    0.000    0.000    0.000 {built-in method builtins.exec} 2    0.000    0.000    0.000    0.000 {built-in method builtins.print} 1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

因此,使用cProfile,所有被调用的函数都会打印出每个函数花费的时间。现在,我们将了解这些列标题的含义:

  • ncalls通话次数 *** tottime在给定功能中花费的总时间 percalltottime除以ncalls的商 cumtime:在此和所有subfunctions中花费的累计时间* percallcumtime除以原语调用的商* filename:lineno(function):提供各功能各自的数据****

****# 时间

timeit是一个 Python 模块,用于为 Python 脚本的一小部分计时。您可以从命令行调用timeit,也可以将timeit模块导入脚本。我们将编写一个脚本来计时一段代码。创建一个timeit_example.py脚本,并将以下内容写入其中:

import timeit prg_setup = "from math import sqrt" prg_code = ''' def timeit_example():
 list1 = [] for x in range(50): list1.append(sqrt(x)) ''' # timeit statement print(timeit.timeit(setup = prg_setup, stmt = prg_code, number = 10000)) 

使用timeit,我们可以决定要测量的代码段的性能。因此,我们可以很容易地定义设置代码以及要单独执行测试的代码段。主代码运行 100 万次,这是默认时间,而设置代码只运行一次。

使程序运行更快

有多种方法可以使 Python 程序运行得更快,例如:

  • 分析代码,以便识别瓶颈
  • 使用内置函数和库,这样解释器就不需要执行循环
  • 避免使用全局变量,因为 Python 访问全局变量的速度非常慢
  • 使用现有软件包

总结

在本章中,我们了解了调试和分析程序的重要性。我们了解了可用于调试的不同技术。我们学习了pdbPython 调试器以及如何处理异常。我们学习了如何在分析和计时脚本时使用 Python 的cProfiletimeit模块。我们还学习了如何使脚本运行得更快。

在下一章中,我们将学习 Python 中的单元测试。我们将学习如何创建和使用单元测试。

问题

  1. 要调试程序,使用哪个模块

  2. 检查如何使用ipython以及所有别名和魔法功能。

  3. 什么是全局解释锁GIL

  4. PYTHONSTARTUPPYTHONCASEOKPYTHONHOMEPYTHONSTARTUP环境变量的目的是什么?

  5. 以下代码的输出是什么?a)[0]、b)[1]、c)[1, 0]、d)[0, 1]

def foo(k):
    k = [1]
q = [0]
foo(q)
print(q)
  1. 以下哪项是无效变量? a】my_string_1 b】1st_string c】foo d】_

进一步阅读