怎樣讓你寫(xiě)的Python代碼更優(yōu)雅?
按照《代碼整潔之道》的說(shuō)法,“花在閱讀和編碼上的時(shí)間比遠(yuǎn)遠(yuǎn)超過(guò) 10:1。”
通常,當(dāng)我們?cè)趯W(xué)校學(xué)習(xí)時(shí),編程美學(xué)不是一個(gè)關(guān)鍵問(wèn)題。用 Python 寫(xiě)代碼時(shí),個(gè)人也會(huì)遵循自己的風(fēng)格。然而,當(dāng)我們必須花大把時(shí)間來(lái)理解一個(gè)人的隱式代碼時(shí),這項(xiàng)工作肯定不受歡迎,這種情況同樣可能發(fā)生在別人閱讀我們的代碼時(shí)。所以,讓我們聚焦 Python 之禪和一些改進(jìn)技巧,從而解決問(wèn)題。
1. Python 之禪?
對(duì)于此前沒(méi)聽(tīng)說(shuō)過(guò)的人,請(qǐng)?jiān)?Python 解釋器中鍵入并執(zhí)行import this,會(huì)出現(xiàn)由 Tim Peters 撰寫(xiě)的 19 條指導(dǎo)原則:
- 優(yōu)美勝于丑陋;
- 明了勝于晦澀;
- 簡(jiǎn)單勝于復(fù)雜;
- 復(fù)雜勝于晦澀;
- 扁平勝于嵌套;
- 間隔勝于緊湊;
- 可讀性很重要;
- 特例不足以特殊到違背這些原則;
- 實(shí)用性勝過(guò)純粹;
- 永遠(yuǎn)不要默默地忽視錯(cuò)誤;
- 除非明確需要這樣做;
- 面對(duì)模棱兩可,拒絕猜測(cè);
- 解決問(wèn)題最直接的方法應(yīng)該有一種,最好只有一種;
- 當(dāng)然這是沒(méi)法一蹴而就的,除非你是荷蘭人;
- 做也許好過(guò)不做;
- 但不想就做還不如不做;
- 如果方案難以描述明白,那么一定是個(gè)糟糕的方案;
- 如果實(shí)現(xiàn)容易描述,那可能是個(gè)好方案;
- 命名空間是一種絕妙的理念,多加利用!
在這篇文章中,我將分享自己對(duì)這些格言的理解以及我學(xué)到的一些有用的 Python 技巧。
2. 優(yōu)美勝于丑陋
Python 具有語(yǔ)法簡(jiǎn)單、代碼可讀性強(qiáng)和命令類(lèi)似英語(yǔ)等特點(diǎn),這讓編寫(xiě) Python 代碼比使用其他編程語(yǔ)言更容易、更高效。例如,使用or and和|| &&構(gòu)建語(yǔ)義相同的表達(dá)式:
- # &&, ||
- if a == 0 && b == 1 || c == True:
- # and, or
- if a == 0 and b == 1 or c == True:
- # 這兩個(gè)邏輯表達(dá)式在 Python 中是相同的
- # 從語(yǔ)義的角度來(lái)看,可以使用選擇操作符來(lái)構(gòu)造完全相同的表達(dá)式。
此外,代碼的布局和組成非常重要,有大量資源涉及這個(gè)主題。下面是最受歡迎也是我最喜歡的一個(gè):PEP 8——Python 代碼風(fēng)格指南。
https://www.python.org/dev/peps/pep-0008/
瀏覽完 PEP8 后,看看下面這些文章,其中展示了一些亮點(diǎn)和應(yīng)用:
- 如何參照 PEP 8 編寫(xiě)漂亮的 Python 代碼
https://realpython.com/python-pep8/
- 優(yōu)雅的 Python 與 PEP8
https://medium.com/@mariasurmenok/stylish-python-with-pep8-c3ca93531418
- PEP-8 的陷阱
https://medium.com/@ian.reinert/the-pitfalls-of-pep-8-b6108b006ed9
永遠(yuǎn)不要弄亂你的代碼。要優(yōu)雅而美麗。
3. 明了勝于晦澀
在 Python 中,良好的命名約定不僅可以提升你的課堂成績(jī),而且還能讓你的代碼更明了。幸運(yùn)的是,你能在 PEP8 中找到一些指導(dǎo)原則,我想在下面強(qiáng)調(diào)其中的一些要點(diǎn)。
https://www.python.org/dev/peps/pep-0008/
- 一般來(lái)說(shuō),避免使用以下名稱(chēng):
- 太寬泛,如my_list;
- 太冗長(zhǎng),如list_of_machine_learning_data_set;
- 太模糊,如“1”、“I”、“o”、“O”。
- 包 / 模塊名應(yīng)該全部小寫(xiě):
- 首選使用一個(gè)單詞命名;
- 當(dāng)需要使用多個(gè)單詞時(shí),使用下劃線分割它們。
- 類(lèi)名應(yīng)遵循 UpperCaseCamelCase 規(guī)范
- 變量\方法\函數(shù)應(yīng)該采用小寫(xiě)(如果需要,用下劃線分割)
- 常量名必須全大寫(xiě)(如果需要,用下劃線分割)
一切都必須清晰易懂。
4. 簡(jiǎn)單勝于復(fù)雜
簡(jiǎn)單比復(fù)雜更難:你必須付出巨大艱辛,化繁為簡(jiǎn)。但這一切到最后都是值得的,因?yàn)橐坏┠阕龅搅?,你便能?chuàng)造奇跡。——喬布斯
很多時(shí)候,在處理迭代器時(shí),我們還需要保存迭代計(jì)數(shù)。Python 通過(guò)提供一個(gè)名為enumerate()的內(nèi)置函數(shù)簡(jiǎn)化這一任務(wù)。以下是一種不成熟的方法,然后是推薦方法:
- words = ['Hannibal', 'Hanny', 'Steeve']
- # 不成熟的方法
- index = 0
- for word in words:
- print(index, word)
- index += 1
- # 推薦方法
- for index, word in enumerate(words):
- print(index, word)
另一個(gè)示例是使用內(nèi)置的zip()函數(shù),該函數(shù)創(chuàng)建一個(gè)迭代器,對(duì)來(lái)自?xún)蓚€(gè)或多個(gè)迭代器的元素進(jìn)行配對(duì)。你可以使用它來(lái)快速有效地解決常見(jiàn)的編程問(wèn)題,比如創(chuàng)建字典。
- subjects = ['math', 'chemistry', 'biology', 'pyhsics']
- grades = ['100', '83', '90', '92']
- grades_dict = dict(zip(subjects, grades))
- print(grades_dict)
化繁為簡(jiǎn)的能力就是消除不必要的東西,保留必要的東西。
5. 復(fù)雜勝于晦澀
復(fù)雜(complex )和晦澀(complicated )的區(qū)別在于,復(fù)雜是指組件的系統(tǒng)層級(jí),晦澀是指難度高。
有時(shí)候,盡管我們?cè)噲D讓任務(wù)變得簡(jiǎn)單和傻瓜化,結(jié)果可能仍然很糟。
在這種情況下,編程優(yōu)化變得很有必要,我最喜歡的學(xué)習(xí)方法是完成 coding challenge websites 上的工作。你可以查看其他人的解決方案,甚至能受到更好算法的啟發(fā)。
https://www.freecodecamp.org/news/the-10-most-popular-coding-challenge-websites-of-2016-fb8a5672d22f/
對(duì)于入門(mén),HackerRank 提供了適合新手程序員的各種級(jí)別任務(wù),這非常棒。之后,可以去嘗試更專(zhuān)業(yè)的網(wǎng)站,比如 Coderbyte 和 Topcoder。
6. 扁平勝于嵌套
嵌套模塊在 Python 中并不常見(jiàn)——至少我之前沒(méi)有見(jiàn)過(guò)像module.class.subclass.function這樣的東西——可讀性不好。雖然在另一個(gè)子模塊中構(gòu)建子模塊可能會(huì)減少代碼行數(shù),但我們不希望用戶(hù)被不直觀的語(yǔ)法所困擾。
7. 間隔勝于緊湊
不要在一行中插入太多代碼,這會(huì)給讀者帶來(lái)壓力。建議最大行長(zhǎng)度 79 個(gè)字符。這樣,當(dāng)使用代碼評(píng)審工具時(shí),編輯器窗口寬度限制才能很好工作。
使用 Python 從 Unsplash 下載圖片
8. 可讀性很重要
代碼的閱讀次數(shù)比編寫(xiě)次數(shù)多??紤]下縮進(jìn),它讓代碼更容易閱讀,比較下面的代碼:
- money = 10000000
- print("I earn", money, "dollars by writing on medium.")
- money = 10_000_000
- print(f"I earn {money} dollars by writing on medium.")
在本例中,代碼結(jié)果相同,但是后一段代碼通過(guò)使用下劃線占位符和 f-string 提供了更好的可讀性。在 Python 3.6 發(fā)布后,f-string 開(kāi)始讓格式化變得更簡(jiǎn)單,并且在處理包含更多變量的更長(zhǎng)的句子時(shí)更強(qiáng)大。
一個(gè)作家的風(fēng)格不應(yīng)該在他的思想和讀者的思想間設(shè)置障礙。
9. 特例不足以特殊到違背這些原則
關(guān)鍵是為一般情況提供一貫支持,嘗試將一個(gè)繁瑣的項(xiàng)目重新組織成一個(gè)簡(jiǎn)單形式。例如,根據(jù)其功能,結(jié)構(gòu)化類(lèi)的代碼或?qū)⑵浞诸?lèi)到不同的文件中,即使 Python 并不強(qiáng)迫你這樣做。由于 Python 是一種多范式編程語(yǔ)言,解決問(wèn)題的一個(gè)強(qiáng)大方法是創(chuàng)建對(duì)象,這就是所謂的面向?qū)ο缶幊獭?/p>
面向?qū)ο缶幊淌且环N組織程序結(jié)構(gòu)的編程范式,讓屬性和行為可以被看作是單獨(dú)對(duì)象。它的優(yōu)點(diǎn)是直觀和易于操作,許多教程都很好地解釋了這些概念。
10. 實(shí)用性勝過(guò)純粹
這句格言與前一句相矛盾,它提醒我們保持它們之間的平衡
11. 永遠(yuǎn)不要默默地忽視錯(cuò)誤
放過(guò)錯(cuò)誤最終會(huì)留下隱式 Bug,并且這些 Bug 更難被發(fā)現(xiàn)。Python 提供了健壯的錯(cuò)誤處理,與其他語(yǔ)言相比,程序員使用該工具并不難。
- try:
- x = int(input("Please enter an Integer: "))
- except ValueError:
- print("Oops! This is not an Integer.")
- except Exception as err:
- print(err)
- else:
- print('You did it! Great job!')
- finally:
- print('ヽ(✿゚▽゚)ノ')
- # 1. 這段代碼可能中斷。
- # 2. 如果出現(xiàn)值錯(cuò)誤就會(huì)觸發(fā)。
- # 3. 處理值錯(cuò)誤之外的錯(cuò)誤。
- # 4. 如果沒(méi)有觸發(fā)錯(cuò)誤就執(zhí)行。
- # 5. 不管是否觸發(fā)錯(cuò)誤都執(zhí)行。
根據(jù) Python 文檔:“即使一個(gè)語(yǔ)句或表達(dá)式在語(yǔ)法上是正確的,在試圖執(zhí)行它時(shí)也可能會(huì)導(dǎo)致錯(cuò)誤。”特別是對(duì)于大型項(xiàng)目,我們不希望在耗時(shí)的計(jì)算后,代碼崩潰。這就是異常管理的魅力所在。
12. 除非明確需要這樣做
在某些情況下,小錯(cuò)誤不會(huì)困擾你。不過(guò),也許你想捕獲特定錯(cuò)誤。要獲得關(guān)于特定錯(cuò)誤消息的更多細(xì)節(jié),我建議閱讀官方的內(nèi)置異常文檔并找到你需要的內(nèi)容。
https://docs.python.org/3/library/exceptions.html
13. 面對(duì)模棱兩可,拒絕猜測(cè)
重要的是要不斷學(xué)習(xí),享受挑戰(zhàn),容忍歧義。我們都不知道最終會(huì)怎樣。——瑪?shù)倌?middot;霍納
這句話優(yōu)雅而抒情,但在編程中不是一個(gè)好的隱喻。歧義可能是指不清楚的語(yǔ)法、復(fù)雜的程序結(jié)構(gòu)或觸發(fā)錯(cuò)誤消息的錯(cuò)誤。例如,第一次使用numpy模塊時(shí)的一個(gè)簡(jiǎn)單錯(cuò)誤:
- import numpy as np
- a = np.arange(5)
- print(a < 3)
- if a < 3:
- print('smaller than 3')
ValueError: 具有多個(gè)元素的數(shù)組的真值不明確,請(qǐng)使用 a.any() 或 a.all()
如果執(zhí)行上面代碼,你將在輸出中發(fā)現(xiàn)一個(gè)由 5 個(gè)布爾值組成的數(shù)組,表明值在 3 以下。因此,if語(yǔ)句不可能確定狀態(tài)。消息中顯示的內(nèi)置函數(shù).all() 和.any()用于代替 And/Or。
- import numpy as np
- a = np.array([True, True, True])
- b = np.array([False, True, True])
- c = np.array([False, False, False])
- print(a.all())
- print(a.any())
- print(b.all())
- print(b.any())
- print(c.all())
- print(c.any())
輸出表明,.all()僅在所有項(xiàng)都為T(mén)rue時(shí)才返回True,而.any()在有一項(xiàng)為T(mén)rue時(shí)就返回True。
14. 解決問(wèn)題最直接的方法應(yīng)該有一種,最好只有一種
想想為什么 Python 被描述為一種易于學(xué)習(xí)的編程語(yǔ)言。Python 具有非凡的內(nèi)置函數(shù) / 庫(kù)和高度的可擴(kuò)展性,它鼓勵(lì)程序員優(yōu)雅地編寫(xiě)代碼。盡管有更多的解決方案可以提供靈活性,但對(duì)于同一個(gè)問(wèn)題,它們可能會(huì)花費(fèi)更多時(shí)間。
輸入 import antigravity 并執(zhí)行
15. 當(dāng)然這是沒(méi)法一蹴而就的,除非你是荷蘭人
Python 之父 Guido van Rossum 是一位荷蘭程序員,他讓這句格言變得無(wú)可爭(zhēng)議。你不會(huì)聲稱(chēng)自己比他更了解 Python……至少我不會(huì)。
照片來(lái)自 GitHub
16. 做也許好過(guò)不做
你可以拖延,但時(shí)間不會(huì),失去的時(shí)間一去不復(fù)返。——本杰明·富蘭克林
對(duì)于那些像我一樣患有拖延癥,正在尋求改變的人,看看這個(gè),和恐慌怪獸合作。
另一方面,這個(gè)格言的另一個(gè)方面是阻止你過(guò)度計(jì)劃,這并不比看 Netflix 更有效率。
拖延和過(guò)度計(jì)劃的共同特征就是“什么都做不了。”
17. 不想就做還不如不做
“做也許好過(guò)不做”并不意味著計(jì)劃沒(méi)用。把你的想法寫(xiě)下來(lái),設(shè)定一個(gè)要征服的目標(biāo),比不想就做要好。
例如,我通常在每個(gè)星期天花一個(gè)小時(shí)來(lái)制定我的周計(jì)劃,并在睡覺(jué)前更新我明天的計(jì)劃,看看有什么需要推遲的事情。
18. 如果解決方案難以解釋清楚,那一定很糟糕
回想一下“復(fù)雜勝于晦澀”的理念。通常,晦澀的代碼意味著弱設(shè)計(jì),特別是在像 Python 這樣的高級(jí)編程語(yǔ)言中。
然而,在某些情況下,其領(lǐng)域知識(shí)的復(fù)雜性可能會(huì)讓實(shí)現(xiàn)難以解釋?zhuān)绾蝺?yōu)化讓其明晰易懂至關(guān)重要。這里有一個(gè)規(guī)劃項(xiàng)目指南,可以給你提供幫助。
https://docs.python-guide.org/writing/structure/
19. 如果實(shí)現(xiàn)容易描述,那可能是個(gè)好方案
使設(shè)計(jì)(甚至人們的生活)更容易,即使背景知識(shí)可能很深刻,這是編程的專(zhuān)業(yè)知識(shí),我認(rèn)為也是編程中最困難的部分。
利用 Python 的簡(jiǎn)單性和可讀性來(lái)實(shí)現(xiàn)一些瘋狂的想法。
20. 命名空間是一種絕妙的理念,多加利用!
最后但同樣重要的是,命名空間是一組符號(hào),用于組織各種對(duì)象,以便這些對(duì)象可以通過(guò)惟一的名稱(chēng)引用。在 Python 中,命名空間是由以下元素組成的系統(tǒng):
- 內(nèi)置命名空間:可以在不創(chuàng)建自定義函數(shù)或?qū)肽K(如print()函數(shù))的情況下調(diào)用。
- 全局命名空間:當(dāng)用戶(hù)創(chuàng)建一個(gè)類(lèi)或函數(shù)時(shí),將創(chuàng)建一個(gè)全局命名空間。
- 局部命名空間:局部作用域中的命名空間。
命名空間關(guān)系圖
命名空間系統(tǒng)可以防止 Python 模塊名稱(chēng)之間產(chǎn)生沖突。