摘要:很多人在編寫Python代碼時,只注重相關(guān)功能的實現(xiàn),并不關(guān)心代碼的整潔性。當軟件的代碼量達到一定規(guī)模后,不少開發(fā)者感到代碼越來越混亂、越來越難以維護。學習Python語言,不能只關(guān)注語法規(guī)則的掌握。通過代碼示例,文章闡述了與提高軟件代碼整潔性相關(guān)的幾個方面內(nèi)容:寫代碼時,要注意可讀性;要撰寫功能單一且功能清晰的函數(shù);應充分利用裝飾器、生成器和迭代器等Python獨有的特性,寫出高效的代碼;應了解并經(jīng)常使用Python軟件質(zhì)量保證工具Unittest、Pytest、Pylint和Flake8等測試和掃描代碼。
關(guān)鍵詞:編程規(guī)范;代碼可讀性;軟件可維護性;單元測試
BriefDiscussiononHowtoWriteCleanPythonCode
ShiYemu
FacultyofAIinEducation,CentralChinaNormalUniversityHubeiWuhan430079
Abstract:WhenwritingPythoncode,manypeopleonlyfocusontheimplementationofrelatedfunctionsanddonotcareaboutthecleanlinessofthecode.Whentheamount?;ofsoftwarecodereachesacertainscale,manydevelopersfeelthatthecodebecomesincreasinglyconfusinganddifficulttomaintain.WhenlearningthePythonlanguage,youcan'tjustfocusonmasteringthegrammaticalrules.Throughexamples,thisarticleexplainsseveralaspectsrelatedtoimprovingthecleanlinessofsoftwarecode:payattentiontoreadabilitywhilewritingcode;writefunctionswithsingleandclearintention;makefulluseofdecorators,generatorsanditerators,whicharePython'sspecialfeatures,towriteefficientcode;youshouldunderstandandfrequentlyusePythonSWqualityassurancetoolssuchasunittest,pytest,pylintandflake8,etc.,totestandscanyourcode.
Keywords:Programmingspecifications;codereadability;softwaremaintainability;unittest
實現(xiàn)同樣的功能,不同的程序員寫出來的代碼完全不一樣,有的人寫的代碼很長,既不容易讀懂又很難維護,軟件的可維護性差,會降低工作效率,相應的項目會越來越難以持續(xù)下去,嚴重的會導致項目組不得不重新設計、編寫代碼。有的軟件作者,在不同時期寫的代碼風格不一樣。有的項目組,每個人都有自己的風格,組內(nèi)風格不統(tǒng)一,大家溝通起來不順暢。這些情況的出現(xiàn),都是因為程序員沒有注意到代碼的整潔性。本文通過舉例,論述了為寫出整潔的代碼而需要注意的幾個地方。
1要有好的可讀性
針對邏輯上并不復雜的功能需求,有的編程者寫的代碼別人很難讀懂,有的人看不明白自己幾個月前寫的代碼,這都是因為代碼的可讀性差??勺x性不好的軟件,維護起來非常不容易。
寫代碼的同時,要寫相應的注釋,寫代碼和寫注釋要做到同步;注釋要有一定的占比,不能可有可無;定義函數(shù)的時候,要有函數(shù)功能和各個參數(shù)說明的注釋;定義類的時候,類的每個屬性和方法都要有相應的注釋;修改代碼的時候,相應的注釋也必須修改。雖然注釋不參與編譯、不參與程序的運行,但注釋非常重要,要把注釋看作是程序的一個組成部分。
除了增加注釋外,文件名、變量名、函數(shù)名和類名等要盡可能地做到顧名思義和一望而知[1],不要讓閱讀代碼的人(包括將來的自己)去猜。例如:
classPerson:
def__init__(self,name,email,phone):
self.name=name
self.email=email
self.phone=phone
顯然,類Person的初始化函數(shù)記錄了人名、郵箱和電話。寫代碼能做到見其名知其意很好,但也不要過度。例如,下面的代碼和前面相比,很詳盡,但并沒有增加可讀性,反而讓人覺得過于啰唆:
classPerson:
def__init__(self,personal_username,personal_email_address,personl_telephone_number):
self.personal_username=personal_username
self.personal_email_address=personal_email_address
self.personal_telephone_num=personal_telephone_number
2函數(shù)功能要單一
假設有如下函數(shù),功能是取得一個列表,然后打印該列表的所有元素:
deffetch_and_show_users():
users=[…]#由某算法得到列表
foruserinusers:
print(user)#顯示列表
這個函數(shù)有兩個功能,但有的用戶只需要得到列表,而有的用戶只想顯示列表。這時,對于某些調(diào)用該函數(shù)的用戶而言,代碼里出現(xiàn)了累贅,這增加了代碼的冗余,多余的步驟對程序的調(diào)試會造成干擾。把前面的函數(shù)分解為如下的兩個函數(shù),非常方便于用戶的調(diào)用:
deffetch_users():
users=[]#由某算法得到列表
returnusers
defdisplay_users(users):
foruserinusers:
print(user)#顯示列表
再舉個例子,某個函數(shù)具備下載、解壓縮和按照某種規(guī)則選取解壓之后文件的功能,最好將這個函數(shù)分解。否則,可能會出現(xiàn)這樣的情況:有的用戶只是想下載某個壓縮包而調(diào)用了該函數(shù),他不得不等著解壓縮和選取文件這兩個他并不需要的步驟完成。
不僅是函數(shù),還有類和模塊,都應功能單一,只做一件事而且要將事情做好[2]。
3函數(shù)功能要清晰
先看如下轉(zhuǎn)換大小寫的函數(shù),該函數(shù)的作用不難理解:
deftransform_text(text,uppercase):
ifuppercase:
returntext.upper()
else:
returntext.lower()
顯然,當調(diào)用transform_text(text,True)時,字符串text中的小寫字母都轉(zhuǎn)換為大寫;當調(diào)用transform_text(text,F(xiàn)alse)時,字符串text中的大寫字母都轉(zhuǎn)換為小寫。如果不看函數(shù)transform_text的定義,僅僅看transform_text(text,True/False),很難知道它在做什么。
將上面的函數(shù)分為如下兩個函數(shù):
defturn_to_uppercase(text):
returntext.upper()
defturn_to_lowercase(text):
returntext.lower()
當調(diào)用turn_to_uppercase(text)時,字符串text中的小寫字母都轉(zhuǎn)換為大寫;當調(diào)用turn_to_lowercase(text)時,字符串text中的大寫字母都轉(zhuǎn)換為小寫。這時,僅僅看turn_to_uppercase(text)或者turn_to_lowercase(text),就知道在做什么。函數(shù)的功能要做到清晰明確[3]。
在調(diào)用函數(shù)遇到關(guān)鍵字參數(shù)的時候,帶上關(guān)鍵字名字可以增強可讀性,而且不必擔心參數(shù)的次序?qū)戝e。例如,SendMail(from=a@x.com,to=b@y.com)比SendMail(a@x.com,b@y.com)的可讀性強很多,用意一目了然,就像在讀簡單明了的英文句子。
4使用更Pythonic的語法
Python代碼要有“Python的味道”,同等條件下,代碼要寫得Pythonic一些。例如,遍歷一個列表,可用如下for循環(huán):
forkinrange(len(a_list)):
print('index:',k,'value:',a_list[k])
上面的代碼是正確的,但這是傳統(tǒng)編程語言的方法。作為Python程序員,應使用針對可迭代對象的內(nèi)置函數(shù)enumerate:
forindex,iteminenumerate(a_list):
print('index:',index,'value:',item)
交換兩個變量a和b的值,傳統(tǒng)語言的代碼如下:
tmp=b
b=a
a=tmp
Python代碼這樣寫也是沒有問題的。實際上,Python語言有如下簡便的寫法,用以交換a和b的值:
a,b=b,a
判斷字符串的前綴與后綴,可以使用內(nèi)置的字符串方法startswith和endswith,也可以使用切片。例如,如下if語句作用是相同的:
sentence="Helloeveryone,goodmorning"
ifsentence.startswith("Hello"):……
ifsentence[:5]=="Hello":……
ifsentence.endswith("morning"):……
ifsentence[-7:]=="morning":……
很明顯,startswith和endswith的可讀性更好,使用切片的話,可讀性下降,而且容易將字母的個數(shù)寫錯。
對一個列表的每個元素進行相同的操作,使用map和列表推導都可以。例如:
>>>nums=[1,2,3,4,5]
>>>squares=list(map(lambdax:x**2,nums))
>>>squares
[1,4,9,16,25]
>>>squares=[x**2forxinnums]
>>>squares
[1,4,9,16,25]
顯然,列表推導的可讀性更強、形式上簡單,而且列表推導的速度比map快,運行效率高。完成同樣的任務,應盡可能使用列表推導而不是map。
5充分利用Python的獨有特性
裝飾器本身是一個函數(shù),裝飾器的返回值是一個函數(shù)對象,裝飾器可以讓其他函數(shù)在不需要做任何代碼變動的前提下增加額外功能[4]。例如,有N個函數(shù),現(xiàn)在要給它們增加函數(shù)日志和函數(shù)性能測試的功能。如果是傳統(tǒng)編程語言,需要對每個函數(shù)進行修改。對于Python而言,不需要進行N次類似甚至同樣的修改。定義一個裝飾器decorator,對函數(shù)進行封裝,添加所需的功能(日志和性能測試),然后在每個函數(shù)的頭部添加一行@decorator即可,N個函數(shù)的原始代碼保持不變。這樣保證了代碼的穩(wěn)定性,在減少重復勞動的同時,增加了函數(shù)的功能。
生成器支持延遲計算,在需要的時候可以生成相應的值,而不是一次性地生成整個序列。生成器特別適合于大型數(shù)據(jù)處理,使用生成器,可以減少內(nèi)存使用,優(yōu)化代碼,提高程序的效率。
6使用Unittest和Pytest進行測試
有的程序員寫了很長時間的代碼,但從未對自己的代碼進行過單元測試,只有在程序運行遇到錯誤時才開始檢查當中的問題。單元測試是用來對函數(shù)、類或者模塊進行正確性檢驗的工作[5]。如果代碼都通過了單元測試,那么軟件的質(zhì)量將大大提高。Unittest是Python自帶的測試框架,例如,如下代碼使用斷言來測試函數(shù)square()是否正確:
importunittest
defsquare(a):
returna*a
classST(unittest.TestCase):
deftest_square(self):
self.assertEqual(square(7),49)
if__name__=="__main__":
unittest.main()
Pytest是一個第三方的測試框架,兼容Unittest,比Unittest框架使用起來更簡潔,效率更高,使用命令pipinstallpytest安裝它。Pytest編寫測試用例很容易,用例可以是類的形式,也可以是函數(shù)的形式。例如,如下代碼用來測試函數(shù)double()的正確性:
defdouble(x):
return3*x
deftest_dbl():
assertdouble(8)==16
運行pytest,結(jié)果為:
deftest_dbl():
>assertdouble(8)==16
Eassert24==16
E+where24=double(8)
test.py:4:AssertionError
根據(jù)輸出,很容易看到問題所在,將函數(shù)double()中的3*x改為2*x就解決了。
7使用Pylint等工具檢查代碼
在項目編碼完成或者階段性完成后,應該使用質(zhì)量保證工具對代碼進行掃描,就像體檢一樣。Pylint是一個針對Python代碼中的語法錯誤、潛在問題和代碼風格的靜態(tài)檢查工具,使用pipinstallpylint命令安裝它。下面的代碼一共三行,看上去沒有任何問題:
defadd_one(x):
returnx+1;
print(add_one(15))
運行命令python3mpylint<文件名>,用Pylint分析這三行看似正確且毫無瑕疵的代碼,得到的結(jié)果為:
2:0:W0311:Badindentation.Found3spaces,expected4(badindentation)
2:0:W0301:Unnecessarysemicolon(unnecessarysemicolon)
1:0:C0114:Missingmoduledocstring(missingmoduledocstring)
1:0:C0116:Missingfunctionormethoddocstring(missingfunctiondocstring)
短短的三行代碼,掃描出了四個問題:(1)第二行的縮進是三個空格,最好是四個空格;(2)第二行結(jié)尾的分號沒有必要;(3)文件沒有注釋說明;(4)函數(shù)也沒有注釋說明。修改如下,再使用Pylint掃描就沒有問題了:
'''Thispythonscriptisforpylintstudy'''
defadd_one(x):
'''inputparameterisanumber,
add1tothenumber,andreturn'''
returnx+1
print(add_one(15))
8學習PEP8
除了語法,程序員也要學習編碼規(guī)范方面的知識。PEP8是Python編碼規(guī)范指南,PEP是PythonEnhancementProposals的簡寫,遵循該規(guī)范可以讓開發(fā)者寫出整潔的代碼,提高代碼的可讀性,有助于同一個項目組內(nèi)大家的編碼風格保持一致[6]。使用工具Pycodestyle可以檢查代碼是否遵從PEP8,運行命令pipinstallpycodestyle安裝它。如下是非常簡短的四行代碼:
a=1
b=2
print(a==b)
if(b>a):print("bisbigger")
運行命令pycodestyle<文件名>,檢測這四行代碼,結(jié)果如下:
1:2:E225missingwhitespacearoundoperator
3:6:E211whitespacebefore'('
4:3:E275missingwhitespaceafterkeyword
4:10:E231missingwhitespaceafter':'
4:10:E701multiplestatementsononeline(colon)
使用Pycodestyle發(fā)現(xiàn)的問題有:a=1的等號兩邊應該有空格;print和(之間的空格是不需要的;關(guān)鍵字if的后面應該有空格;最后一行最好分為兩行。將代碼修改如下,再使用Pycodestyle掃描就沒有問題了:
a=1
b=2
print(a==b)
ifb>a:
print("bisbigger")
工具Flake8的功能比Pycodestyle更加強大,安裝命令為pipinstallflake8。Flake8將Pyflakes(類似于Pylint)、Pycodestyle和McCabe(代碼復雜性檢查器)整合到一起,使用它可以一次性檢查出多種問題。Flake8非常易于與其他工具結(jié)合,比如,在集成開發(fā)環(huán)境PyCharm中配置Flake8非常簡單。
結(jié)語
Python誕生于1991年,近些年,Python語言已經(jīng)滲透到各個領(lǐng)域,使用Python的人越來越多。Python用戶不能僅僅學語法,還應該了解如何讓代碼變得更整潔。軟件的維護期一般長于(甚至遠遠長于)開發(fā)期,不整潔的代碼很難提高工作效率。作為Python開發(fā)人員,要想寫出整潔的代碼,除了語法規(guī)則之外,需要了解的知識點比較多,很難用一篇短文完全闡述清楚。希望本文能給新Python程序員一點啟發(fā),為代碼質(zhì)量的提升提供些許幫助。
參考文獻:
[1]HarshitTyagi.PythonicCode:BestPracticestoMakeYourPythonMoreReadable[EB/OL].(20220530).https://www.codementor.io/blog/pythoniccode6yxqdoktzt.
[2]AlexOmeyer.10MustKnowPatternsforWritingCleanCodeWithPython[EB/OL].(20220406).https://dzone.com/articles/10mustknowpatternsforwritingcleancodewith1.
[3]KhuyenTran.PythonCleanCode:6BestPracticestoMakeYourPythonFunctionsMoreReadable[EB/OL].(20210121).https://towardsdatascience.com/pythoncleancode6bestpracticestomakeyourpythonfunctionsmorereadable7ea4c6171d60.
[4]蘇尼爾·卡皮爾.Python代碼整潔之道編寫優(yōu)雅的代碼[M].連少華,譯.北京:機械工業(yè)出版社,2020.
[5]馬里西諾·阿納亞.編寫整潔的Python代碼[M].包永帥,譯.北京:人民郵電出版社,2021.
[6]ThePEPEditors.IndexofPythonEnhancementProposals[EB/OL].(20000713).https://peps.python.org/.
作者簡介:石也牧(2004—),女,漢族,北京人,本科,研究方向:人工智能。