寫代碼怎樣才能如魚得水
寫代碼怎樣才能如魚得水取決于你寫代碼的態(tài)度,首先很多人在寫代碼的過(guò)程中可能真的只注重了寫代碼,沒(méi)有過(guò)多的思考,也不會(huì)對(duì)代碼進(jìn)行調(diào)試或性能上的分析,僅僅是為了完成或?qū)崿F(xiàn)某個(gè)功能而去寫代碼痹兜,這樣的寫代碼是沒(méi)有靈魂的,你不會(huì)得到絲毫的進(jìn)步颤诀,長(zhǎng)久下去也只能淪為碼農(nóng)而不是工程師字旭。
那么如何才能做到是事半功倍的編寫代碼,并能在每一次的開(kāi)發(fā)中得到提升和進(jìn)步呢崖叫?其實(shí)說(shuō)起來(lái)也不難遗淳,那就是每次對(duì)編寫的代碼進(jìn)行調(diào)試和性能分析,并深度思考進(jìn)行程序優(yōu)化提升心傀,以此來(lái)提升你的編寫能力屈暗,冰凍三尺非一日之寒,這事情雖然簡(jiǎn)單但是卻需要強(qiáng)大的執(zhí)行力脂男。但如果不想淪為碼農(nóng)或成為社畜养叛,就需要你的行動(dòng)了。接下來(lái)就為大家分享一下如何對(duì)代碼進(jìn)行調(diào)試和性能分析助你一臂之力宰翅。
代碼調(diào)試
說(shuō)到代碼調(diào)試弃甥,很多同學(xué)會(huì)問(wèn),那還不簡(jiǎn)單嗎汁讼?print()不就可以了嗎淆攻?你說(shuō)的對(duì),我們會(huì)在開(kāi)發(fā)的過(guò)程中大量使用print來(lái)對(duì)代碼進(jìn)行一些輸出測(cè)試嘿架,以確保程序是否達(dá)到預(yù)期瓶珊,這是一種非常簡(jiǎn)單粗暴的快速debug的方式,但是這種方法也僅限于小型程序耸彪。
我們會(huì)對(duì)每一個(gè)代碼塊進(jìn)行print輸出來(lái)調(diào)試是否達(dá)到預(yù)期伞芹,但是如果程序比較大,每次都需要重新運(yùn)行才能看到打印的結(jié)果搜囱,那成本肯定就很高丑瞧,特別還有一些通常需要反復(fù)運(yùn)行調(diào)試才能找到錯(cuò)誤的根源柑土,這樣如果僅僅使用print()打印那效率就很低了蜀肘。
當(dāng)然,除了使用print之外稽屏,我們還可以借助于IDE來(lái)完成debug扮宠。比如Pycharm,就可以在代碼中設(shè)置斷點(diǎn),這樣只要程序運(yùn)行到斷點(diǎn)就會(huì)自動(dòng)停止坛增,并可以方便的查看變量的值获雕,當(dāng)然也推薦大家使用這樣的方式進(jìn)行debug。
但是也有一些情況是Pycharm無(wú)法滿足的收捣,比如Pycharm只能針對(duì)本項(xiàng)目中的py代碼進(jìn)行調(diào)試届案,另外很多進(jìn)行數(shù)據(jù)分析、數(shù)據(jù)挖掘和AI算法的代碼其實(shí)大多數(shù)都是在 Jupyter 的 Notebook 中罢艾。那么這種情況就下Pycharm就無(wú)能無(wú)力了楣颠。因此今天我們?yōu)榇蠹曳窒硪粋€(gè)Python中自帶的高效的代碼調(diào)試庫(kù)pdb,那么它是什么咐蚯?又如何使用呢童漩?
使用pdb進(jìn)行代碼調(diào)試
pdb是python自帶的代碼調(diào)試庫(kù),是一個(gè)交互式源代碼調(diào)試器春锋。它支持在源代碼行級(jí)別設(shè)置斷點(diǎn)和單步執(zhí)行矫膨、堆棧幀檢查、源代碼列表以及在任何堆棧幀的上下文中評(píng)估任意Python代碼期奔。
Python官方文檔地址:https://docs.python.org/3/library/pdb.html#module-pdb
如何使用 pdb
要啟動(dòng) pdb 調(diào)試侧馅,我們只需要在程序中,加入import pdb
和pdb.set_trace()
這兩行代碼就行了
a = 1
b = 2
import pdb
pdb.set_trace()
c = 3
print(a + b + c)
接下來(lái)我們運(yùn)行這個(gè)代碼能庆,它的輸出界面是下面這樣的施禾,表示程序已經(jīng)運(yùn)行到了pdb.set_trace()
這行,并且暫停了下來(lái)搁胆,等待輸入指令弥搞。
> /Users/yc/Desktop/code/demo.py(5)<module>()
-> c = 3
(Pdb)
這時(shí),我們就可以執(zhí)行一些調(diào)試渠旁,比如打印變量的語(yǔ)法是p <expression>
(pdb) p a # 輸入 p a 等于要查看變量a的值
1
(pdb) p b
2
(pdb) p c # 因?yàn)槌绦蚰壳爸贿\(yùn)行了前面幾行攀例,此時(shí)的變量 c 還沒(méi)有被定義:
*** NameError: name 'c' is not defined
如果需要程序繼續(xù)執(zhí)行下一行可以輸入n
,表示繼續(xù)執(zhí)行代碼到下一行
(pdb) n
-> print(a + b + c)
查看當(dāng)前代碼行附近的源代碼可以輸入l
顾腊,列出當(dāng)前代碼行上下的 11 行源代碼
(pdb) l
1 a = 1
2 b = 2
3 import pdb
4 pdb.set_trace()
5 -> c = 3
6 print(a + b + c)
pdb常用指令參考
完整指令 | 簡(jiǎn)寫指令 | 指令描述 |
---|---|---|
break | b <行號(hào)> | 在某一行設(shè)置斷點(diǎn) |
break | b <文件名>:<行號(hào)> | 在某個(gè)文件的某行打一點(diǎn)斷點(diǎn) |
break | b <函數(shù)名> | 在某個(gè)含稅的第一行打一個(gè)斷點(diǎn) |
clear | cl | 清除所有斷點(diǎn) |
clear | cl n1 n2 | 清除編號(hào)為n1粤铭、n2的斷點(diǎn) |
next | n | 執(zhí)行下一條語(yǔ)句,遇到函數(shù)不進(jìn)入其內(nèi)部 |
step | s | 執(zhí)行下一條語(yǔ)句杂靶,遇到函數(shù)會(huì)進(jìn)入其內(nèi)部 |
continue | c或者cont | 執(zhí)行到下一個(gè)斷點(diǎn) |
list | l | 列出源碼(前后11行代碼) |
list | l <行號(hào)> | 列出某行周圍11行代碼(即這一行為中間行梆惯,列出它上下各5行) |
list | l <行號(hào)1> <行號(hào)2> | 列出兩個(gè)行號(hào)范圍內(nèi)的代碼 |
p | p | 打印變量值,也可以用print |
pp | pp | 好看一點(diǎn)的輸出 |
quit | q | 退出 pdb |
return | r | 一直運(yùn)行到函數(shù)返回 |
以上就是給大家做了一個(gè)簡(jiǎn)單的pdb使用方法吗垮,除了這些常用命令垛吗,還有許多其他的命令可以使用,這里我就不在一一贅述了烁登∏犹耄可以參考對(duì)應(yīng)的官方文檔,來(lái)熟悉這些用法。
用 cProfile 進(jìn)行性能分析
那么除了要對(duì)程序進(jìn)行調(diào)試锨络,性能分析也是每個(gè)開(kāi)發(fā)者的必備技能赌躺。
日常工作中,我們常常會(huì)遇到這樣的問(wèn)題:在線上羡儿,我發(fā)現(xiàn)產(chǎn)品的某個(gè)功能模塊效率低下礼患,延遲(latency)高,占用的資源多掠归,但卻不知道是哪里出了問(wèn)題讶泰。這時(shí),對(duì)代碼進(jìn)行性能分析就顯得非常重要拂到,通過(guò)性能分析你就可以知道程序的瓶頸所在痪署,從而對(duì)其進(jìn)行修正或優(yōu)化。當(dāng)然兄旬,這并不需要你花費(fèi)特別大的力氣狼犯,在 Python 中,這些需求用 cProfile 就可以實(shí)現(xiàn)领铐。
舉個(gè)例子悯森,比如我想實(shí)現(xiàn)一個(gè)斐波拉契數(shù)列,運(yùn)用遞歸思想绪撵,我們很容易就能寫出下面這樣的代碼:
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def fib_seq(n):
res = []
if n > 0:
res.extend(fib_seq(n-1))
res.append(fib(n))
return res
fib_seq(30)
接下來(lái)瓢姻,我想要測(cè)試一下這段代碼總的效率以及各個(gè)部分的效率。
那么音诈,我就只需在開(kāi)頭導(dǎo)入 cProfile 這個(gè)模塊幻碱,并且在最后運(yùn)行 cProfile.run() 就可以了:
import cProfile
def fib(n):...
def fib_seq(n):...
cProfile.run('fib_seq(30)')
或者更簡(jiǎn)單一些,直接在運(yùn)行腳本的命令中细溅,加入選項(xiàng)“-m cProfile”也很方便:
python3 -m cProfile xxx.py
運(yùn)行完畢后褥傍,我們可以看到下面這個(gè)輸出界面:
7049218 function calls (96 primitive calls) in 2.280 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 2.280 2.280 <string>:1(<module>)
31/1 0.000 0.000 2.280 2.280 demo.py:10(fib_seq)
7049123/31 2.280 0.000 2.280 0.074 demo.py:3(fib)
1 0.000 0.000 2.280 2.280 {built-in method builtins.exec}
31 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
30 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
輸出的第一行
7049218 function calls (96 primitive calls) in 2.280 seconds
是指本次運(yùn)行總計(jì)7049218個(gè)函數(shù)調(diào)用被監(jiān)控,其中96個(gè)是原生調(diào)用(不涉及遞歸)
其它列信息:
ncalls喇聊,是指相應(yīng)代碼 / 函數(shù)被調(diào)用的次數(shù)恍风;
tottime,是指對(duì)應(yīng)代碼 / 函數(shù)總共執(zhí)行所需要的時(shí)間(注意誓篱,并不包括它調(diào)用的其他代碼 / 函數(shù)的執(zhí)行時(shí)間)朋贬;
- tottime percall,就是上述兩者相除的結(jié)果窜骄,也就是tottime / ncalls锦募;
- cumtime,則是指對(duì)應(yīng)代碼 / 函數(shù)總共執(zhí)行所需要的時(shí)間啊研,這里包括了它調(diào)用的其他代碼 / 函數(shù)的執(zhí)行時(shí)
- cumtime percall御滩,則是 cumtime 和 ncalls 相除的平均結(jié)果。
了解這些參數(shù)后党远,再來(lái)看這個(gè)結(jié)果削解。我們可以清晰地看到,這段程序執(zhí)行效率的瓶頸沟娱,在于第二行的函數(shù) fib()氛驮,它被調(diào)用了 700 多萬(wàn)次。
有沒(méi)有什么辦法可以提高改進(jìn)呢济似?答案是肯定的矫废。通過(guò)觀察,我們發(fā)現(xiàn)砰蠢,程序中有很多對(duì) fib() 的調(diào)用蓖扑,其實(shí)是重復(fù)的,那我們就可以用字典來(lái)保存計(jì)算過(guò)的結(jié)果台舱,防止重復(fù)律杠。
改進(jìn)后的代碼如下所示:
def funcopt(f):
opt = {}
def helper(x):
if x not in opt:
opt[x] = f(x)
return opt[x]
return helper
@funcopt
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def fib_seq(n):
res = []
if n > 0:
res.extend(fib_seq(n-1))
res.append(fib(n))
return res
fib_seq(30)
這時(shí),我們?cè)賹?duì)其進(jìn)行 profile
216 function calls (128 primitive 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 chuange.py:1(<module>)
1 0.000 0.000 0.000 0.000 chuange.py:1(funcopt)
31/1 0.000 0.000 0.000 0.000 chuange.py:16(fib_seq)
89/31 0.000 0.000 0.000 0.000 chuange.py:3(helper)
31 0.000 0.000 0.000 0.000 chuange.py:8(fib)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
31 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
30 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects}
此時(shí)你會(huì)看到由原來(lái)的700萬(wàn)次的函數(shù)調(diào)用優(yōu)化到了216次竞惋,性能的提高自然就不用多說(shuō)了柜去。
這個(gè)簡(jiǎn)單的例子,便是 cProfile 的基本用法拆宛,也是我今天分享的重點(diǎn)嗓奢。當(dāng)然,cProfile 還有很多其他功能浑厚,還可以結(jié)合 stats 類來(lái)使用股耽,你可以閱讀相應(yīng)的 官方文檔 來(lái)了解。
總結(jié)
本章節(jié)我們?yōu)榇蠹曳窒砹?Python 中常用的調(diào)試工具 pdb钳幅,和經(jīng)典的性能分析工具 cProfile豺谈。pdb 為 Python 程序提供了一種通用的、交互式的高效率調(diào)試方案贡这;而 cProfile 則是為開(kāi)發(fā)者提供了每個(gè)代碼塊執(zhí)行效率的詳細(xì)分析茬末,有助于我們對(duì)程序的優(yōu)化與提高。關(guān)于它們的更多用法盖矫,你可以通過(guò)它們的官方文檔進(jìn)行實(shí)踐丽惭,熟能生巧。
最后希望各位小伙伴們寫代碼事半功倍辈双,如魚得水责掏。
如果喜歡或者對(duì)你有幫助的小伙伴,歡迎大家關(guān)注我的公眾號(hào):后廠程序員湃望,并分享换衬、點(diǎn)贊痰驱、在看 三連