精通Python自动化脚本-运维人员宝典完整目录:
第一章 Python脚本概述
第二章 Python脚本调试和性能测试
第三章 单元测试-单元测试框架的介绍
第四章 自动化常规运维活动
第五章 文件、目录和数据处理
第六章 文件存档、加密和解密
第七章 文本处理和正则表达式
第八章 文档和报告
第九章 操作各类文件
第十章 网络基础 – Socket编程
第十一章 使用Python脚本处理邮件
第十二章 使用Telnet和SSH远程监控主机
第十三章 创建图形化用户界面
第十四章 处理Apache和其它的日志文件
第十五章 SOAP和REST API通讯
第十六章 网络抓取 – 从网站上提取有用的信息
第十七章 数据收集及报表
第十八章 MySQL和SQLite数据库管理
什么是调试?
调试(debugging)是一个解决代码中错误或导致软件不能正常运行的问题的过程。Python中的调试非常容易。Python调试器设置条件断点并对源码逐行调试。我们将使用Python标准库中的 pdb 模块来对我们的Python脚本进行调试。
Python 的调试技术
为更好的调试Python程序,可以使用不同的技术。我们就来看看Python调试的四种技术:
- print()语句:这是了解具体发生情况的最简单的方式,这样我们可以检查执行的内容
- logging:这类似于print语句但带更多的上下文信息,因此我们可以更全面的了解情况
- pdb调试器:这是最常使用的调试技术。使用 pdb 的优势是能够在命令行、解释器以及程序中使用 pdb
- IDE调试器:IDE有内置的调试器。这让开发者可以执行自己的代码,然后开发者可以在程序执行过程中检查代码
错误处理(异常处理)
在这一部分中我们将学习Python如何处理异常。但首先什么是异常呢?异常是在程序执行过程中发生的错误。每当错误发生时,Python会生成一个异常,使用try…except代码块来进行处理。有时异常程序无法处理,因此会导致报错信息。下面我们就来看一些异常的示例:
在你的终端中,启动python3交互控制台,我们一起来看一些异常示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
>>> 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的语法如下:
1 2 3 4 |
try: statement(s) except: statement(s) |
一个try代码块可带有多个except语句。我们可通过在except关键字之后输入异常的名称来处理指定的异常。处理指定异常的语法如下:
1 2 3 4 |
try: statement(s) except exception_name: statement(s) |
下面我们创建一个exception_example.py脚本来捕获ZeroDivisionError。在脚本中编写如下代码:
1 2 3 4 5 6 7 8 9 10 |
a = 35 b = 37 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") |
像下面这样运行脚本,将会得到如下结果:
1 2 3 4 |
vagrant@python-scripting:~$ python3 exception_example.py The value of c is: 72 Division by zero is not possible Out of try...except block |
调试器工具
Python中支持很多种调试工具:
- winpdb
- pydev
- pydb
- pdb
- gdb
- pyDebug
这一部分中,我们将学习pdb Python调试器。pdb是Python标准库的一部分并一直可以直接使用。
pdb调试器
pdb模块用于调试Python程序。Python程序使用pdb交互源代码调试器来调试程序。pdb设置断点并检查栈帧,列出源代码。
下面我们将学习如何使用pdb调试器。使用这一调试器有三种方式:
- 在解释器之中
- 通过命令行
- 在Python脚本中
我们将创建一个pdb_example.py脚本并在该脚本中添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 |
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交互控制台。运行如下命令来启动控制台:
1 |
$ python3 |
导入我们的pdb_example脚本名和pdb模块。下面我们将使用run(),并且我们会传入一个字符串表达式来作为run()的参数,由Python解释器自身进行运行:
1 2 3 4 5 |
>>> import pdb_example >>> import pdb >>> pdb.run('pdb_example.Student(5).print_std()') > <string>(1)<module>() (Pdb) |
要继续调试,在(Pdb)提示符之后输入continue并按下Enter(或直接输入 h并回车)。我果想要了解这里可以使用的选项,在(Pdb)提示符之后按下两次Tab键。
在输入continue之后,我们将得到如下的输出:
1 2 3 4 5 6 7 8 9 10 11 |
>>> import pdb_example >>> import pdb >>> pdb.run('pdb_example.Student(5).print_std()') > <string>(1)<module>() (Pdb) continue 0 1 2 3 4 >>> |
命令行调试
运行调试器最简单也最直接的方式是通过命令行。我们的程序将作为调试器的输入。我们可以这样在命令行中使用调试器:
1 |
$ python3 -m pdb pdb_example.py |
在从命令行运行调试器时,源代码会被载入并在调试器找到的第一行停止执行。输入continue来继续调试。输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
vagrant@python-scripting:~$ python3 -m pdb pdb_example.py > /home/vagrant/pdb_example.py(1)<module>() -> class Student: (Pdb) continue 0 1 2 3 4 The program finished and will be restarted > /home/vagrant/pdb_example.py(1)<module>() -> class Student: (Pdb) |
Python脚本内调试
以上两种技术会在Python程序开始时启动调试器。但第三种方法对于长期处理来说最佳。要在脚本中启动调试器,使用set_trace()。
现在修改pdb_example.py文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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) return if __name__ == "__main__": Student(5).print_std() |
现在运行程序如下:
1 2 3 4 5 6 7 8 |
vagrant@python-scripting:~$ python3 pdb_example.py > /home/vagrant/pdb_example.py(10)print_std() -> print(i) (Pdb) continue 0 > /home/vagrant/pdb_example.py(9)print_std() -> pdb.set_trace() (Pdb) |
set_trace()是一个Python函数,因此可以在程序的任意处调用它。所以我们有三种方式来启动调试器。
基本程序崩溃调试
在这一部分中,我们来看看trace模块。trace模块有助于追踪程序的执行。因此不论何时程序崩溃,我们都能了解在哪里出现的崩溃。我们可以在脚本中导入也可以通过命令行来使用trace模块。
现在我们将创建一个名为trace_example.py的脚本并在该脚本中编写如下代码:
1 2 3 4 5 6 7 8 9 10 11 |
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() |
输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
vagrant@python-scripting:~$ 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_example.py(6): for i in range(self.count): trace_example.py(8): return --- modulename: trace, funcname: _unsettrace trace.py(77): sys.settrace(None) |
因此通过在命令行中使用trace –trace,开发人员可以对程序逐行追踪。这样在程序崩溃时,开发人员就会知道发生崩溃的实例。
程序性能和时耗分析
对Python程序进行性能分析(profiling)表示度量程序的执行时间。它计量每个函数所花的时间。Python的cProfile模块用于对Python程序进行性能分析。
cProfile模块
正如前文所讲到的,性能分析表示度量程序的执行时间。我们就来使用cProfile Python模块对程序进行性能分析。
现在来编写一个cprof_example.py脚本并加入如下代码:
1 2 3 4 5 6 7 8 |
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) |
运行程序,将会看到如下的输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
vagrant@python-scripting:~$ python3 -m cProfile cprof_example.py Local Value: 4466 Global Value: 0 6 function calls in 0.001 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.001 0.001 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.001 0.001 {built-in method builtins.exec} 2 0.001 0.000 0.001 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: 给定函数花费的总时间
- percall: tottime除以ncalls所得的商
- cumtime: 当前以及其子函数所花费的累计时间
- percall: cumtime除以原始调用所得的商
- filename:lineno(function): 提供函数各自的数据
timeit
timeit是一个Python模块,用于对Python脚本的各部分进行计时。我们可以在命令行中调用timeit,也可以在脚本中导入timeit模块。下面我们来编写一个脚本来对代码片断进行计时。创建一个timeit_example.py脚本并编写如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 |
import timeit prg_setup = "from math import sqrt" prg_code = ''' def timeit_example(): list1 = [] for x in range(50): list1.append(sqrt(x)) ''' # timeit 语句 print(timeit.timeit(setup = prg_setup, stmt = prg_code, number = 10000)) |
运行结果:
1 2 |
vagrant@python-scripting:~$ python3 timeit_example.py 0.0010215669999524835 |
使用timeit,,我们可以决定要对哪段代码进行性能的度量。因此,我们可以轻易地定义setup代码来作为我们想单独执行测试的代码片断。主代码默认运行100万次,但setup代码仅运行一次。
加速程序运行
有很多方式来让Python程序运行得更快,比如:
- 对认定为瓶颈的代码进行性能分析
- 使用内置函数和库,这样解释器不用执行不同循环
- 避免使用全局变量,因为Python在访问全局变量时速度很慢
- 使用已有的包
总结
在本章中,我们学习了调试程序和性能分析的重要性。还学习了用于调试的不同技术。我们学习了pdb Python调试器以及如何处理异常。还学习了如何使用Python中的cProfile和timeit模块来对脚本进行性能和时耗分析。最后我们学习了如何加速脚本的运行。
下一章中,我们将学习Python中的单元测试。我们会学习如何创建和使用单元测试。
课后问题
- 要调试程序,使用哪个模块?
点击查看调试程序,使用pdb模块。
- 查看如何在ipython中使用所有的别名函数和魔法函数。
点击查看使用%lsmagic,在运行ipython3之前,请执行sudo apt-get install ipython3进行安装
- 什么是全局解释器锁(Global interpreter lock (GIL))?
点击查看全局解释器锁是一种计算机语言解释器用于同步线程执行的机制,这样同一时间只有一个原生线程在执行。
- PYTHONPATH, PYTHONCASEOK, PYTHONHOME和PYTHONSTARTUP环境变量的目的是什么?
点击查看- PYTHONPATH: 作用类似于PATH。该变量告诉Python解释器从何处查找导入程序的模块文件。它应包含Python源代码库目录并包含Python源代码。 PYTHONPATH有时由Python安装器进行预设。
- PYTHONSTARTUP: 它包含含有Python源代码初始化文件的路径。在每次启动解释器时进行执行。在Unix中名称为.pythonrc.py,包含加载工具类或修改PYTHONPATH的一些命令。
- PYTHONCASEOK: 在Windows中用于指引Python查找import语句中第一个忽略大小写的匹配文件。可将此变量设为任意值进行启用。
- PYTHONHOME: 另一个模块搜索路径。通常内嵌于PYTHONSTARTUP或PYTHONPATH目录中来使用得模块库切换更为简易。
- 以下代码的输出是什么?
a) [0]
b) [1]
c) [1, 0]
d) [0, 1]
12345def foo(k):k = [1]q = [0]foo(q)print(q)
点击查看答案是r:[0]
在函数中创建了新的列表对象,失去了引用。可通过在k = [1]的前台对比k的 ID 来进行校验。 - 以下哪个是无效变量?
a) my_string_1
b) 1st_string
c) foo
d) _
点击查看答案是b. 变量量不可以数字开头。
扩展阅读
- 如何处理 Python 中的GIL问题:https://realpython.com/python-gil/
- 查看如何在命令行中使用pdb模块:https://fedoramagazine.org/getting-started-python-debugger/
下表转自菜鸟教程:
变量名 | 描述 |
---|---|
PYTHONPATH | PYTHONPATH是Python搜索路径,默认我们import的模块都会从PYTHONPATH里面寻找。 |
PYTHONSTARTUP | Python启动后,先寻找PYTHONSTARTUP环境变量,然后执行此变量指定的文件中的代码。 |
PYTHONCASEOK | 加入PYTHONCASEOK的环境变量, 就会使python导入模块的时候不区分大小写. |
PYTHONHOME | 另一种模块搜索路径。它通常内嵌于的PYTHONSTARTUP或PYTHONPATH目录中,使得两个模块库更容易切换。 |
以下内容转自百度百科:
全局解释器锁(Global Interpreter Lock)是计算机程序设计语言解释器用于同步线程的工具,使得在同一进程内任何时刻仅有一个线程在执行。常见例子有CPython(JPython不使用GIL)与Ruby MRI。详情
- Python的线程是操作系统线程。在Linux上为pthread,在Windows上为Win thread,完全由操作系统调度线程的执行。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即使在多核CPU平台上,由于GIL的存在,所以禁止多线程的并行执行。
- Python解释器进程内的多线程是合作多任务方式执行。当一个线程遇到I/O任务时,将释放GIL。计算密集型(CPU-bound)的线程在执行大约100次解释器的计步(ticks)时,将释放GIL。计步(ticks)可粗略看作Python虚拟机的指令。计步实际上与时间片长度无关。可以通过sys.setcheckinterval()设置计步长度。
- 在单核CPU上,数百次的间隔检查才会导致一次线程切换。在多核CPU上,存在严重的线程颠簸(thrashing)。
- Python 3.2开始使用新的GIL。
- 可以创建独立的进程来实现并行化。