精通Python自動化腳本-運維人員寶典完整目錄:
第一章 Python腳本概述
第二章 Python腳本調(diào)試和性能測試
第三章 單元測試-單元測試框架的介紹
第四章 自動化常規(guī)運維活動
第五章 文件罐盔、目錄和數(shù)據(jù)處理
第六章 文件存檔彩扔、加密和解密
第七章 文本處理和正則表達式
第八章 文檔和報告
第九章 操作各類文件
第十章 網(wǎng)絡(luò)基礎(chǔ) - Socket編程
第十一章 使用Python腳本處理郵件
第十二章 使用Telnet和SSH遠程監(jiān)控主機
第十三章 創(chuàng)建圖形化用戶界面
第十四章 處理Apache和其它的日志文件
第十五章 SOAP和REST API通訊
第十六章 網(wǎng)絡(luò)抓取 - 從網(wǎng)站上提取有用的信息
第十七章 數(shù)據(jù)收集及報表
第十八章 MySQL和SQLite數(shù)據(jù)庫管理
什么是調(diào)試铺浇?
調(diào)試(debugging)是一個解決代碼中錯誤或?qū)е萝浖荒苷_\行的問題的過程。Python中的調(diào)試非常容易。Python調(diào)試器設(shè)置條件斷點并對源碼逐行調(diào)試确虱。我們將使用Python標(biāo)準(zhǔn)庫中的 pdb 模塊來對我們的Python腳本進行調(diào)試隘世。
Python 的調(diào)試技術(shù)
為更好的調(diào)試Python程序,可以使用不同的技術(shù)怯邪。我們就來看看Python調(diào)試的四種技術(shù):
- print()語句:這是了解具體發(fā)生情況的最簡單的方式绊寻,這樣我們可以檢查執(zhí)行的內(nèi)容
- logging:這類似于print語句但帶更多的上下文信息,因此我們可以更全面的了解情況
- pdb調(diào)試器:這是最常使用的調(diào)試技術(shù)悬秉。使用 pdb 的優(yōu)勢是能夠在命令行澄步、解釋器以及程序中使用 pdb
- IDE調(diào)試器:IDE有內(nèi)置的調(diào)試器。這讓開發(fā)者可以執(zhí)行自己的代碼和泌,然后開發(fā)者可以在程序執(zhí)行過程中檢查代碼
錯誤處理(異常處理)
在這一部分中我們將學(xué)習(xí)Python如何處理異常村缸。但首先什么是異常呢?異常是在程序執(zhí)行過程中發(fā)生的錯誤武氓。每當(dāng)錯誤發(fā)生時梯皿,Python會生成一個異常,使用try…except代碼塊來進行處理县恕。有時異常程序無法處理东羹,因此會導(dǎo)致報錯信息。下面我們就來看一些異常的示例:
在你的終端中忠烛,啟動python3交互控制臺属提,我們一起來看一些異常示例:
>>> 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'
這就是異常的一些示例。下面我們來看如何處理這些異常美尸。
每當(dāng)Python程序中發(fā)生錯誤時冤议,就會拋出異常。我們也可使用raise關(guān)鍵字來強制拋出異常师坎。
下來我們來看一個處理異常的try…except代碼塊恕酸。在try代碼塊中,我們將編寫可能生成異常的代碼胯陋。在except代碼塊中蕊温,我們將編寫異常的處理方式。try…except的語法如下:
try:
statement(s)
except:
statement(s)
一個try代碼塊可帶有多個except語句惶岭。我們可通過在except關(guān)鍵字之后輸入異常的名稱來處理指定的異常寿弱。處理指定異常的語法如下:
try:
statement(s)
except exception_name:
statement(s)
下面我們創(chuàng)建一個exception_example.py腳本來捕獲ZeroDivisionError。在腳本中編寫如下代碼:
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")
像下面這樣運行腳本按灶,將會得到如下結(jié)果:
vagrant@python-scripting:~$ python3 exception_example.py
The value of c is: 72
Division by zero is not possible
Out of try...except block
調(diào)試器工具
Python中支持很多種調(diào)試工具:
- winpdb
- pydev
- pydb
- pdb
- gdb
- pyDebug
這一部分中症革,我們將學(xué)習(xí)pdb Python調(diào)試器。pdb是Python標(biāo)準(zhǔn)庫的一部分并一直可以直接使用鸯旁。
pdb調(diào)試器
pdbs模塊用于調(diào)試Python程序噪矛。Python程序使用pdb交互源代碼調(diào)試器來調(diào)試程序量蕊。pdb設(shè)置斷點交檢查棧幀,列出源代碼艇挨。
下面我們將學(xué)習(xí)如何使用pdb調(diào)試器残炮。使用這一調(diào)試器有三種方式:
- 在解釋器之中
- 通過命令行
- 在Python腳本中
我們將創(chuàng)建一個pdb_example.py腳本并在該腳本中添加如下內(nèi)容:
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()
使用這一腳本作為學(xué)習(xí)Python調(diào)試的示例,我們將了解如何啟動調(diào)試器的細節(jié)缩滨。
解釋器內(nèi)調(diào)試
要從Python交互控制臺中啟動調(diào)試器势就,我們使用run()或runeval()。
啟動python3交互控制臺脉漏。運行如下命令來啟動控制臺:
$ python3
導(dǎo)入我們的pdb_example腳本名和pdb模塊苞冯。下面我們將使用run(),并且我們會傳入一個字符串表達式來作為run()的參數(shù)侧巨,由Python解釋器自身進行運行:
>>> import pdb_example
>>> import pdb
>>> pdb.run('pdb_example.Student(5).print_std()')
> <string>(1)<module>()
(Pdb)
要繼續(xù)調(diào)試舅锄,在(Pdb)提示符之后輸入continue并按下Enter。我果想要了解這里可以使用的選項司忱,在(Pdb)提示符之后按下兩次Tab鍵皇忿。
在輸入continue之后,我們將得到如下的輸出:
>>> import pdb_example
>>> import pdb
>>> pdb.run('pdb_example.Student(5).print_std()')
> <string>(1)<module>()
(Pdb) continue
0
1
2
3
4
>>>
命令行調(diào)試
運行調(diào)試器最簡單也最直接的方式是通過命令行坦仍。我們的程序?qū)⒆鳛檎{(diào)試器的輸入鳍烁。我們可以這樣在命令行中使用調(diào)試器:
$ python3 -m pdb pdb_example.py
在從命令行運行調(diào)試器時,源代碼會被載入并在調(diào)試器找到的第一行停止執(zhí)行繁扎。輸入continue來繼續(xù)調(diào)試老翘。輸出如下:
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腳本內(nèi)調(diào)試
以上兩種技術(shù)會在Python程序開始時啟動調(diào)試器。但第三種方法對于長期處理來說最佳锻离。要在腳本中啟動調(diào)試器,使用set_trace()墓怀。
現(xiàn)在修改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)
return
if __name__ == "__main__":
Student(5).print_std()
現(xiàn)在運行程序如下:
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函數(shù)汽纠,因此可以在程序的任意處調(diào)用它。所以我們有三種方式來啟動調(diào)試器傀履。
基本程序崩潰調(diào)試
在這一部分中虱朵,我們來看看trace模塊。trace模塊有助于追蹤程序的執(zhí)行钓账。因此不論何時程序崩潰碴犬,我們都能了解在哪里出現(xiàn)的崩潰。我們可以在腳本中導(dǎo)入也可以通過命令行來使用trace模塊梆暮。
現(xiàn)在我們將創(chuàng)建一個名為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()
輸出如下:
>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服协,開發(fā)人員可以對程序逐行追蹤。這樣在程序崩潰時啦粹,開發(fā)人員就會知道發(fā)生崩潰的實例偿荷。
程序性能和時耗分析
對Python程序進行性能分析(profiling)表示度量程序的執(zhí)行時間窘游。它計量每個函數(shù)所花的時間。Python的cProfile模塊用于對Python程序進行性能分析跳纳。
cProfile模塊
正如前文所講到的忍饰,性能分析表示度量程序的執(zhí)行時間。我們就來使用cProfile Python模塊對程序進行性能分析寺庄。
現(xiàn)在來編寫一個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)
運行程序艾蓝,將會看到如下的輸出:
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斗塘,所有調(diào)用的函數(shù)都被打印赢织,并包含各個函數(shù)所消耗的時間。下面我們來看看這些列頭的含義:
- ncalls: 調(diào)用次數(shù)
- tottime: 給定函數(shù)花費的總時間
- percall: tottime除心ncalls所得的商
- cumtime: 當(dāng)前以及其子函數(shù)所花費的累計時間
- percall: cumtime除以原始調(diào)用所得的商
- filename:lineno(function): 提供函數(shù)各自的數(shù)據(jù)
timeit
timeit是一個Python模塊逛拱,用于對Python腳本的各部分進行計時敌厘。我們可以在命令行中調(diào)用timeit,也可以在腳本中導(dǎo)入timeit模塊朽合。下面我們來編寫一個腳本來對代碼片斷進行計時俱两。創(chuàng)建一個timeit_example.py腳本并編寫如下內(nèi)容:
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))
運行結(jié)果:
vagrant@python-scripting:~$ python3 timeit_example.py
0.0010215669999524835
使用timeit,,我們可以決定要對哪段代碼進行性能的度量曹步。因此宪彩,我們可以輕易地定義setup代碼來作為我們想單獨執(zhí)行測試的代碼片斷。主代碼默認運行100萬次讲婚,但setup代碼僅運行一次尿孔。
加速程序運行
有很多方式來讓Python程序運行得更快,比如:
- 對認定為瓶頸的代碼進行性能分析
- 使用內(nèi)置函數(shù)和庫筹麸,這樣解釋器不用執(zhí)行不同循環(huán)
- 避免使用全局變量活合,因為Python在訪問全局變量時速度很慢
- 使用已有的包
總結(jié)
在本章中,我們學(xué)習(xí)了調(diào)試程序和性能分析的重要性物赶。還學(xué)習(xí)用于調(diào)試的不同技術(shù)白指。我們學(xué)習(xí)了pdb Python調(diào)試器以及如何處理異常。還學(xué)習(xí)了如何使用Python中的cProfile和timeit模塊來對腳本進行性能和時耗分析酵紫。最后我們學(xué)習(xí)了如何加速腳本的運行告嘲。
下一章中,我們將學(xué)習(xí)Python中的單元測試奖地。我們會學(xué)習(xí)如何創(chuàng)建和使用單元測試橄唬。
課后問題
- 要調(diào)用程序,使用哪個模塊参歹?
- 查看如何在ipython中使用所有的別名函數(shù)和魔法函數(shù)仰楚。
- 什么是全局解釋器鎖(Global interpreted lock (GIL))?
- PYTHONSTARTUP, PYTHONCASEOK, PYTHONHOME和PYTHONSTARTUP環(huán)境變量的目的是什么?
- 以下代碼的輸出是什么缸血?a) [0], b) [1], c) [1, 0], d) [0, 1]
def foo(k):
k = [1]
q = [0]
foo(q)
print(q)
- 以下哪個是無效變量蜜氨?
a) my_string_1
b) 1st_string
c) foo
d) _
擴展閱讀
- 如何處理 Python 中的GIL問題:https://realpython.com/python-gil/
- 查看如何在命令行中使用pdb模塊:https://fedoramagazine.org/getting-started-python-debugger/
本文首發(fā)地址:Alan Hou 的個人博客