01.01 如何給你的Python代碼“減負”,這裡有一份指南請查收

全文共6616字,預計學習時長

19分鐘

如何給你的Python代碼“減負”,這裡有一份指南請查收


數據科學團隊傾向於向兩個互相競爭的領域發展。


一方面,有一些數據工程師非常看重高度可靠,穩固的代碼,這些代碼承擔的技術債務少。另一方面,有些數據科學家非常重視概念驗證(e.g.設置)中想法和算法的快速原型設計。


雖然更成熟的數據科學功能使雙方之間擁有卓有成效的工作夥伴關係,建立了完善的CI / CD管道,並明確定義了職責分工,但早期團隊通常由經驗不足的數據科學家主導。所以,代碼質量受到損害,技術債務以膠合代碼,流水線叢林,無效的實驗代碼路徑和配置債務的形式呈指數級累積[1]。

如何給你的Python代碼“減負”,這裡有一份指南請查收

你能想象沒有xkcd的生活嗎?


最近,我寫了一篇關於為什麼數據科學家的代碼傾向於遭受平庸之苦的評論文章,在這篇文章中,我希望介紹一些方法供新手數據科學家編寫更清晰的Python代碼並更好地構建小型項目,以及闡明減少技術債務在不經意間給你和所在團隊帶來的負作用。


下面的內容既不詳盡也不足夠嚴謹,旨在以淺顯的方式介紹深層次建立數據科學項目的方式。有些要點很明顯,有些則有點隱晦。


以下是本文內容的快速概述:(1)樣式準則,(2)文檔,(3)類型檢查,(4)項目文件夾結構,(5)代碼版本控制,(6)模型版本控制,(7)環境,(8)Jupyter筆記本,(9)單元測試,(10)記錄。


Python 代碼樣式指引——PEP 8和linting


可讀性非常重要,PEP8就是為此而打造,提供了編寫簡潔python代碼的慣例。


符合PEP8規範是Pythonic代碼的最基本要求。它表明你已經瞭解了Python開發人員的最基本預期。表明你能夠與其他開發人員更輕鬆地協同合作,最重要的是,你的代碼將更具可讀性和一致性,並且更加方便自己理解。


在這裡複製和重新格式化PEP8樣式指南屬於無用功。因此,你可以根據自己的喜好瀏覽pep8.org,查看示例並瞭解在微觀層面(與在宏觀層面或系統級別上編寫簡潔代碼相反)上編寫簡潔代碼的意義。


PEP8中提供的示例包括設置命名約定,縮進,導入和行長的標準。


順便說一句, PEP8是應使用成熟的IDE(如看來像高級Python IDE的PyCharm)而非Sublime這樣的簡單文本編輯器來編寫代碼的原因之一。適用於Python的重量級IDE通常會遵循PEP8樣式指南,它會在違反PEP8原則或自動重新格式化代碼庫時發出警告。


以下是四個(儘管實際上還有許多種)命令行工具,通過對源代碼執行靜態分析,以保持其簡潔和一致性:


1. PyLint-最受歡迎的linter。它能夠檢查源代碼,並充當錯誤和質量檢查器。它比PEP8具有更多的驗證檢查和選項。但是,根據默認設置,它的輸出有點過於繁瑣,輸出量偏大。

2. Black-自動重新格式化Python代碼。 Black將重新格式化整個文件的格式,並格式化字符串以使其添上雙引號。

3. PyCodeStyle——官方的linter工具,用於根據PEP8 python的樣式規範檢查python代碼。

4. Flake8——封裝pyflakes,pycodestyle和mccabe,它會驗證封裝pep8的封裝器,pyflakes和循環複雜性。


旁註1. linter不會告訴你變量的命名方式是否正確。這項遭新手開發人員嘲笑的技能仍是值得掌握的技能。

旁註2. 在安裝這些軟件包之前,最好先處於虛擬環境中。此點會在後文闡述。

如何給你的Python代碼“減負”,這裡有一份指南請查收


記錄項目— PEP257 and Sphynx


PEP8概述了Python的編碼規範,PEP257則對文檔字符串的高級結構,語義和約定進行了標準化:基本內容及表達方式。與PEP8一樣,這些並不是硬性規定,但是你應該明智地遵循這些準則。


如果違背了這些規範,那麼代碼看起來可能不太美觀。


那文檔字符串又是什麼呢?


docstring是一個字符串文字,作為模塊,函數,類或方法定義中的第一條語句出現。這樣的文檔字符串賦予該對象__doc__特殊屬性。與PEP8一樣,我並不會複製並重新格式化整個PEP格式,你應該在其他時間瀏覽它,此處僅提供函數文檔字符串的兩個示例。


1. 函數添加的單行文檔字符串示例


  1. defadd(a, b):
  2. """Sum twonumbers."""
  3. return a + b

2.複合函數的多行文檔字符串示例:


  1. defcomplex(real=0.0, imag=0.0):
  2. """Form a complexnumber.

  3. Keyword arguments:
  4. real -- the real part (default 0.0)
  5. imag -- the imaginary part (default0.0)
  6. """
  7. if imag == 0.0 and real == 0.0:
  8. return complex_zero
  9. ...

Sphynx


畢竟,格式正確的文檔字符串已經到位了,接下來,你要將其轉換為美化的項目文檔。Sphynx是一款分解Python文檔的生成器,它可以輸出html,pdf,unix幫助頁面等等。


這是Sphynx入門的很好的教程。


簡而言之,在某個目錄(通常是docs目錄)中初始化Sphynx並設置其配置後,使用reStructuredText(* .rst)文件,在調用make之後,這些文件將轉換為首選的輸出文檔類型。


另外,可以直接從文檔字符串創建對Python程序其他部分的引用,這些字符串在輸出文檔中顯示為鏈接。


為了說明Sphynx文檔生成器的典型輸出,此處是一個利用Sphynx生成文檔的Python項目列表,雖然並不完整,但也足夠學習了。示例包括matplotlib, networkX, Flask和 pandas。


類型檢查 — PEP484, PEP526, 和 mypy


由於Python是一種動態類型的語言,因此默認情況下不會進行靜態類型檢查,優點是靈活性和快速的開發進度,但是也有弊端,因為不會在運行系統(編譯時)之前捕獲簡單的錯誤,而是在運行時捕獲它們。通常,像Python這樣的動態類型化語言比靜態類型化語言需要更多的單元測試。這很繁瑣。在諸如Scala或Java或C#之類的語言中,類型聲明以代碼形式出現,並且編譯器檢查變量是否合法通過類型傳遞。


最終,靜態類型扮演安全網的角色,在某些情況下你應該明智地使用。好消息是Python實際上提供了所謂的類型提示。


這是一個函數註釋類型提示的示例。這意味著name應為str類型,函數也應返回str。


  1. defgreeting(name: str) -> str:
  2. return 'Hello ' + name

這是變量註釋類型提示的示例。這意味著變量i應該是整數。請注意,以下語法適用於Python 3.6及更高版本。


  1. i: int =5

但壞消息是,類型解釋被Python解釋器忽略,並且沒有運行時的效果(PEP當前仍在進行中)。


但是,如果沒有運行時效果,為什麼還要使用類型提示呢?有兩個原因。首先,代碼的作用更明確,知道如何運行。其次,由於可以在Python中使用靜態類型檢查器,因此默認情況下它不會運行。主要的靜態類型檢查器是mypy。


旁註1:由於類型提示的可選性,可以將它們放在代碼中的任何位置,某些位置或不放置。試圖使其與放置位置保持一致。

旁註2:如前所述,PyCharm能很好地施行PEP8標準。但是Pycharm在類型提示的情況下也同樣很有用。它會自動檢查類型提示,並在違背預期時給予提示。默認情況下,在PyCharm中,這些設置為警告提示,但也可以將其設置為錯誤提示。

如何給你的Python代碼“減負”,這裡有一份指南請查收


項目文件夾結構— cookiecutter


正如凌亂的辦公桌映射著頭腦混亂,凌亂的文件夾結構也是如此。


從一開始,許多項目就受益於經過深思熟慮的目錄結構。不幸的是,開始項目的一種常見方法是創建一個基本項目目錄,並將所有內容直接歸於目錄中——從數據到筆記本到模型生成再到輸出,而不考慮由於簡單項目變得越來越複雜的參數化管道產生的不良影響。


最終,會面臨某種形式的技術債務,此後必須以時間和精力去償還。真的完蛋了嗎?只需從一開始有遠見地正確構造項目,就能避免這些。


我們為此苦苦掙扎的一部分原因是因為創建項目文件夾結構很繁瑣。我們想深入探索數據並建立機器學習模型。但是相對較小的精力投入可以節省很多工作。


精簡的文件夾結構促進最佳實踐,簡化關注點分離,並使學習(或重新學習)舊代碼更加愉悅。


幸運的是,由於開源者的辛勤工作,有一種剛出爐的現成解決方案,用於創建我們想要的文件夾結構:cookiecutter。


創建許多數據科學項目共有的簡潔文件夾結構只是一個命令。觀看下面的視頻,瞭解如何設置cookiecutter項目結構。

cookiecutter-數據科學

由 ericmjalbertasciinema.org錄製


請注意,cookiecutter功能非常強大,實際上除了生成簡潔項目文件夾結構外,還有更多功能。有關更多信息,請查看優秀的cookiecutter文檔,它詳實地闡明瞭數據科學項目的基本原理。


代碼版本控制— git


這一點廣為人知,我將不作贅述。軟件開發的現代世界已經遠離2000年代以前陰暗的狂野西部。每個人和他的下屬都應該使用某種版本控制其項目。簡單的協作,高效的版本控制,回溯和代碼備份,這就足夠了。


學會使用git是第一步,而用好git則是另外一碼事了。


提交代碼:“提早提交併經常提交”是合理的建議。避免提交大量代碼,而建議提交小的獨立功能。提交時,編寫描述性的提交消息以準確記錄更改。


多用戶最佳實踐:這在很大程度上取決於具體情境。我與團隊合作的方式是擁有一個永遠不會直接推送的master分支(主分支),一個正在運行代碼的運行版本但從未直接對其進行開發的develop分支(開發分支),然後是團隊中各個成員進行編碼的feature分支(功能分支),這些功能隨後合併到開發分支。當develop分支準備發佈時,它會合併到master分支。這種封裝的工作方式使多個開發人員在不干擾主代碼庫的情況下即可輕鬆地處理特定功能,降低了合併衝突的可能性。有關更多信息,請查看此鏈接。


模型和數據的版本控制— dvc

如何給你的Python代碼“減負”,這裡有一份指南請查收


模型和數據與代碼不同,因此絕不應將其歸入代碼存儲庫中。它們具有獨特的生命週期管理要求,並具有不同的操作約束。但是,適用於代碼版本控制的同一原理同樣適用於數據和模型版本控制。


有一個優秀的開源工具dvc,它基於git構建。其本質上是一個數據管道構建工具,在一定程度上有助於解決數據科學中的重現性危機。它可以有效地將你的數據和模型推送到服務器,無論是本地數據,AWS S3,GCS,Azure,SSH,HDFS還是HTTP。


dvc 有3個核心主題:


1.大文件的版本控制

2.內置可重現的輕質管道

3.基於git的版本控制和實驗管理

旁註:版本控制整個數據集的另一種方法是存儲重新創建這些數據集所需的元數據,並在該參考元數據的背面創建模型引用。


基於環境構建— virtualenv


如果不在常規練習手冊中劃分環境,那麼平衡系統範圍的庫版本可能會花費一到兩個下午。也許你正在處理一個項目,然後移至另一個更新了numpy和poof的項目!那麼第一個項目的依賴關係中斷。


想象一下另一種情況,項目是由另一名團隊成員從git撤出的,該團隊成員正在使用項目中某個庫的其他版本。他們編寫了一些代碼,這些代碼依賴於自己版本缺乏的新功能,然後將其歸為主分支(明智的選擇是永遠不要直接歸為主分支)。你的代碼因此中斷了。


通過使用虛擬環境避免這種情況。對於簡單的Python項目,請使用virtualenv。如果有複雜的環境需求,請使用類似docker的工具。這是一個簡單的virtualenv工作流程:


1.在創建新項目時運行mkvirtualenv

2. pip安裝分析所需的軟件包

3.運行pip Frozen>requirements.txt來固定用於重新創建分析的確切軟件包版本

4.如果發現需要安裝另一個軟件包,請再次運行pip Frozen>requirements.txt並將更改提交給版本控制。


關於Notebooks的提醒 — jupytext


Jupyter Notebooks廣泛應用於數據科學。它們圍繞可讀性高的編程範例構建,並充當強大的媒介,使快速原型製作和易開發性融合,中間代碼段與輸出和說明文字交織,生成精美的演示文稿,非常美觀。


但是,儘管Jupyter Notebooks有各種優勢,也帶來了不少的麻煩。你的計算機存儲了多少個未命名的7.ipynb文件?Jupyter Notebooks最大的缺點可能就是它們與版本控制的配合太低效了。


原因是因為它們屬於一類編輯器——“所見即所得”,此編輯軟件允許用戶查看與最終結果非常相似的內容。這就意味著該文檔正在提取元數據,而Jupyter Notebooks是通過將代碼封裝在大型JSON數據結構中來嵌入代碼的,其中包含二進制數據,如保存為base-64編碼blob的圖像。


這裡聲明一下,Git可以處理Notebook,因為你可以將它們推送到存儲庫中,但在比較不同版本的Notebooks時,Git無法很好地進行處理,並且難以提供對其中代碼的可靠分析。


如果在公司內的Notebooks中或是在公開的GitHub上四處尋找信息,則很可能會發現數據庫憑據,敏感數據,“請勿運行此單元格”代碼塊以及許多其他不良做法。為了避免這種麻煩,可以在每次準備登入Notebooks時都清除輸出。但這這意味著每次都必須手動重新運行代碼以生成輸出,如果有多個用戶使用同一Notebooks,已清除的筆記本元數據甚至也會發生變化。


這兒有多種工具可供選擇。最受歡迎的一種是jupytext。這是作者撰寫的很棒的教程,介紹瞭如何使用它。你所要做的就是安裝jupytext,它將提供一個整潔的筆記本內下拉列表,用於下載代碼的markdown版本,並省略輸出,然後忽略.gitignore中的所有. ipynb文件。


單元測試— unittest


對代碼進行單元測試可能是確保獨立代碼塊按預期工作的有效方法。它允許自動化測試過程,及早發現錯誤,使過程更加敏捷,並最終幫助你設計更好的系統。python中最常用的測試框架是“unitest”的內置電池和內置模塊,該模塊提供了豐富的工具來構建和運行測試。另一個測試工具是pytest。


關於如何進行單元測試的教程有很多,本文提供一些關鍵的技巧。就像異常處理可以最大程度地減少你要捕獲潛在錯誤的功能一樣,每個單元測試都應側重於一小部分功能以證明其正常工作。每個單元測試應完全獨立,並且可以單獨運行。測試套件應在開發新功能之前或之後運行;實際上,在將代碼推送到共享存儲庫之前,使用hook以自動運行所有測試是一個好主意,這種類型的測試通常是某些CI / CD管道的一部分,開源代碼示例服務之一叫做travis。


嘗試加快測試速度吧!對每個測試函數使用長的描述性名稱。測試應位於源代碼的單獨目錄中,有關更多信息,請參見文件夾結構部分。


這裡是一份教程,這是一份測試風格指南


日誌記錄 — PEP282,


日誌記錄是超越POC領域系統的關鍵部分。日誌記錄可以跟蹤程序執行過程中發生的情況並將該信息保存到磁盤。通過保存瀏覽路徑記錄,它極大程度地促進調試並識別錯誤。


日誌記錄通常用於兩個目的。診斷日誌記錄了與應用程序操作相關的事件。而審計日誌記錄了用於MI報告的基本運行時分析。每個日誌消息有幾種類型:debug、info、warning、error和critical。


logging是用於日誌記錄的內置Python標準庫。

如何給你的Python代碼“減負”,這裡有一份指南請查收


結語


如果堅持閱讀到了結尾,那非常值得鼓勵。希望你能有所收穫。現在趕快去編程實踐吧。

如何給你的Python代碼“減負”,這裡有一份指南請查收

我們一起分享AI學習與發展的乾貨


分享到:


相關文章: