Python 編碼風格指南

原文:http://python.jobbole.com/84618/html

本文超出 PEP8 的範疇以涵蓋我認爲優秀的 Python 風格。本文雖然堅持己見,卻不偏執。不只僅涉及語法、模塊佈局等問題,同時深刻範式、組織及架構的領域。但願本文能成爲精簡版 Python 代碼《風格的要素》python

目次

基本聽從 PEP 準則

…… 可是,命名和單行長度更靈活。git

PEP8 涵蓋了諸如空格、函數/類/方法之間的換行、import、對已棄用功能的警告之類的尋常東西,大都不錯。github

應用這些準則的最佳工具是 flake8,還能夠用來發現一些愚蠢的語法錯誤。web

PEP8 本來只是一組指導原則,沒必要嚴格甚至虔誠地信奉。必定記得閱讀 PEP8 「愚蠢的一致性就是小人物的小妖精」一節。若要進一步瞭解,能夠聽一下 Raymond Hettinger 的精彩演講,「超越 PEP8」正則表達式

惟一引發過多爭議的準則事關單行長度和命名。要調整起來也不難。數據庫

靈活的單行長度

如果厭煩 flake8 死板的單行長度不得超過 79 個字符的限制,徹底能夠忽略或修改這一準則。這仍然不失爲一條不錯的經驗法則,就像英語中句子不能超過 50 個單詞,段落不能超過 10 個句子之類的規則同樣。這是 flake8 配置文件 的連接,能夠看到 max-line-length配置選項。值得注意的是,能夠給要忽略 flake8 檢查的那一行加上 # noqa 註釋,可是請勿濫用。編程

儘管如此,超過九成的代碼行都不該該超過 79 個字符,緣由很簡單,「扁平勝於嵌套」。若是函數每一行都超出了 79 個字符,確定有別的東西出錯了,這時要看看代碼而不是 flake8 配置。json

一致的命名

關於命名,遵循幾條簡單的準則就能夠避免衆多足以影響整個小組的麻煩。vim

推薦的命名規則

下面這些準則大多改編自 Pacoo 小組

  • 類名:駝峯式 和首字母縮略詞:HTTPWriter 優於 HttpWriter
  • 變量名:lower_with_underscores
  • 方法名和函數名:lower_with_underscores
  • 模塊名:lower_with_underscores.py。(可是不帶下劃線的名字更好!)
  • 常量名:UPPER_WITH_UNDERSCORES
  • 預編譯的正則表達式:name_re

一般都應該遵循這些準則,除非要參照其餘工具的命名規範,好比數據庫 schema 或者消息格式。

還能夠用 駝峯式 給相似類卻不是類的東西命名。使用 駝峯式 的主要好處在於讓人們以「全局名詞」來關注某個東西,而不是看做局部標記或動詞。值得注意的是,Python 給 TrueFalse 和 None 這些根本不是類的東西命名也是用 駝峯式

不要用前綴後綴

…… 好比 _prefix 或 suffix_ 。函數和方法名能夠用 _prefix 標記來暗示其是「私有的」,可是最好只在編寫預期會普遍使用的 API 以及用 _prefix 標記來隱藏信息的時候謹慎使用。

PEP8 建議使用結尾的下劃線來避免與內置關鍵字重名,好比:

臨時這樣用也能夠,不過最好仍是選一個別的名字。

用 __mangled 這種雙下劃線前綴給類/實例/方法命名的狀況很是少,這實際上涉及特殊的名字修飾,很是罕見。不要起 __dunder__ 這種格式的名字,除非要實現 Python 標準協議,好比 __len__;這是爲 Python 內部協議保留的命名空間,不該該在其中增長自定義的東西。

不要用單字符名字

(不過)一些常見的單字符名字能夠接受。

在 lambda 表達式中,單參數函數能夠命名爲 x 。好比:

解包元組時能夠用 _ 丟棄不須要的標記。好比:

意思就是說「忽略第一個元素」。

和 lambda 相似,在解析列表/字典/集合的時候,以及在生成器表達式或者一到兩行的 for 循環中,可使用單字符迭代標記。一般選擇 x,好比:

能夠求 items 序列中全部正整數之和。

此外比較常見的是 i,表明 index,一般和內置的 枚舉 一塊兒使用。好比:

除卻上述情形,要極少甚至避免使用單字符用做標記/參數/方法的名字。由於這樣就沒法用grep 進行檢索了。

使用 self 及相似的慣例

應該:

  • 永遠將方法的第一個變量命名爲 self
  • 永遠將 @classmethod 的第一個參數命名爲 cls
  • 永遠在變量參數列表中使用 *args 和 **kwargs

不要在這些地方吹毛求疵

不遵循以下準則沒有什麼好處,乾脆照它說的作。

永遠繼承自 object 並使用新式類

 

對於 Python 2 來講遵循這條準則很重要。不過因爲 Python 3 全部的類都隱式繼承自 object,這條準則就沒有必要了。

不要在類中重複使用實例標記

 

 

列表/字典/集合推導式優於 map/filter

 

儘管在大多數簡單狀況下最好使用解析表達式,不過有時候 map() 或者 filter() 可讀性更佳,須要本身判斷。

用圓括號 (...) 折行

 

 

用圓括號 (...) 寫 API 更利落

 

 

函數調用中使用隱式行延續

 

 

用 isinstance(obj, cls), 不要用 type(obj) == cls

由於 isinstance 涵蓋更多情形,包括子類和抽象基類。同時,不要過多使用 isinstance,由於一般應該使用鴨子類型!

用 with 處理文件和鎖

with 語句可以巧妙地關閉文件並釋放鎖,哪怕是在觸發異常的狀況下。因此:

 

和 None 相比較要用 is

None 值是一個單例,可是檢查 None 的時候,實際上不多真的要在 左值上調用 __eq__。因此:

好的寫法不只執行更快,並且更準確。使用 == 並不會更簡潔,因此請記住本規則!

不要修改 sys.path

經過 sys.path.insert(0, "../") 等操做來控制 Python 的導入方法或許讓人心動,可是要堅定避免這樣作。

Python 有一套有幾分複雜,卻易於理解的模塊路徑解決方法。能夠經過 PYTHONPATH 或諸如 setup.py develop 的技巧來調整 Python 導入模塊的方法。還能夠用 -m 運行 Python 獲得須要的效果,好比使用 python -m mypkg.mymodule 而不是 python mypkg/mymodule.py。代碼可否正常運行不該依賴於當前執行 Python 的工做路徑。David Beazley 用 PDF 幻燈片再一次扭轉了你們的見解,值得一讀,「Modules and Packages: Live and Let Die!」

儘可能不要自定義異常類型

…… 若是必定要,也不要建立太多。

要知道 Python 引入了 豐富的內建異常類。值得充分利用。並且經過描述那些觸發特定錯誤條件的字符串消息,來實例化這些異常類,就能達到「定製」的目的。在用戶代碼中拋出 ValueError(參數錯誤),LookupError(鍵錯誤)以及 AssertionError(用 assert 語句)最爲常見。

至因而否應該本身建立異常類有一個不錯的經驗法則,也就是搞清楚函數調用方每次調用函數之時是否都應該捕獲該異常。若是是,那麼的確應該本身建立異常類。不過這至關少見。關於這類明顯不得不使用的自定義異常類有一個不錯的例子,tornado.web.HTTPError。可是要留心 Tornado 是如何避免走極端的:框架或用戶代碼拋出的全部 HTTP 錯誤同屬一個異常類。

短文檔字符串應是名副其實的單行句子

 

把三引號 """ 放在同一行,首字母大寫,以句號結尾。四行精簡到兩行,__doc__ 屬性沒有糟糕的換行,最吹毛求疵的人也會滿意的!

文檔字符串使用 reST

標準庫和大多數開源項目皆是如此。Sphinx 提供支持,開箱即用。趕忙試試吧!Python requests 模塊由此取得了極佳的效果。看看requests.api 模塊的例子。

刪除結尾空格

最挑剔也不過如此了吧,但是若作不到這一點,有些人可能會被逼瘋。不乏能自動搞定這一切的編輯器;這是我用 vim 的實現

文檔字符串要寫好

下面是在函數文檔字符串中使用 Sphinx 風格的 reST 的快速參考:

不要爲文檔而寫文檔。寫文檔字符串要這樣思考:

也就是說,上例中沒有必要說 timeout 是 float,默認值 5.0,顯然是 float。在文檔中指出其語義是「秒」更有用,就是說 5.0 意思是 5 秒鐘。同時調用方不知道 qsargs 應該是什麼,因此用 type 註釋給出提示,調用方也無從知道函數的預期返回值是什麼,因此 rtype註釋是合適的。

最後一點。吉多·範羅蘇姆曾說過,他對 Python 的主要領悟是「讀代碼比寫代碼頻率更高」。直接結論就是有些文檔有用,更多的文檔有害

基本上只須要給預計會頻繁重用的函數寫文檔。若是給內部模塊的每個函數都寫上文檔,最後只能獲得更加難以維護的模塊,由於重構代碼之時文檔也要重構。不要「船貨崇拜」文檔字符串,更不要用工具自動生成文檔。

範式和模式

是函數仍是類

一般應該用函數而不是類。函數和模塊是 Python 代碼重用的基本單元,仍是最靈活的形式。類是一些 Python 功能的「升級路徑」,好比實現容器,代理,描述符,類型系統等等。可是一般函數都是更好的選擇。

或許有人喜歡爲了更好地組織代碼而將關聯的函數歸在類中。但這是錯的。關聯的函數應該歸在模塊中。

儘管有時能夠把類看成「小型命名空間」(好比用 @staticmethod)比較有用,一組方法更應該對同一個對象的內部操做有所貢獻,而不只僅做爲行爲分組。

與其建立 TimeHelper 類,帶有一堆不得不引入子類才能使用的方法,永遠不如直接爲時間相關的函數建立 lib.time 模塊。類會增殖出更多的類,會增長複雜性,下降可讀性。

生成器和迭代器

生成器和迭代器是 Python 中最強大的特性 —— 應該掌握迭代器協議,yield 關鍵字和生成器表達式。

生成器不只僅對要在大型數據流上反覆調用的函數十分重要,並且可讓自定義迭代器更加簡單,從而簡化了代碼。將代碼重構爲生成器一般能夠在使得代碼在更多場景下複用,從而簡化代碼。

Fluent Python 的做者 Lucinao Ramalho 經過 30 分鐘的演講,「迭代器和生成器: Python 之道」,給出了一個出色的,快節奏的概述。Python Essential Reference 和 Python Cookbook 的做者 David Beazley 有個深奧的三小時視頻教程,「生成器:最後的前沿」,給出了使人知足的生成器用例的詳細闡述。由於應用普遍,掌握這一主題很是有必要。

聲明式仍是命令式

聲明式編程優於命令式編程。代碼應該代表你想要作什麼,而不是描述如何去作。Python 的函數式編程概覽介紹了一些不錯的細節並給出了高效使用該風格的例子。

使用輕量級的數據結構更好,好比 列表字典元組集合。將數據展開,編寫代碼對其進行轉換,永遠要優於重複調用轉換函數/方法來構建數據。

一個例子是重構常見的列表解析:

應該改爲 :

可是還有一個不錯的例子是將 if/elif/else 鏈改爲 字典 查詢。

「純」函數和迭代器更好

這是個從函數式編程社區借來的概念。這種函數和迭代器亦被描述爲「無反作用」,「引用透明」或者有「不可變輸入/輸出」。

一個簡單的例子,要避免這種代碼:

這個函數應該從新寫成:

這是個驚人的例子。函數不只更加純粹,並且更加精簡了。不只更加精簡,並且更好。這裏的純粹是說 assert dedupe(items) == dedupe(items) 在「好」版本中恆爲真。在「壞」版本中, num_dupes 在第二次調用時爲 0,這會在使用時致使難以理解的錯誤。

這個例子也闡明瞭命令式風格和聲明式風格的區別:改寫後的函數讀起來更像是對須要的東西的描述,而不是構建須要的東西的一系列操做。

簡單的參數和返回值類型更好

函數應該儘量處理數據,而不是自定義的對象。簡單的參數類型更好,好比字典集合元組列表intfloat 和 bool。從這些擴展到標準庫類型,好比 datetimetimedeltaarrayDecimal 以及 Future。只有在真的必要時才使用自定義類型。

判斷函數是否足夠精簡有個不錯的經驗法則,問本身參數和返回值是否老是能夠 JSON 序列化。結果證實這個經驗法則至關有用:能夠 JSON 序列化一般是函數在並行計算時可用的先決條件。可是,就本文檔而言,主要的好處在於:可讀性,可測試性以及整體的函數簡單性。

避免「傳統的」面向對象編程

在「傳統的面向對象編程語言」中,好比 Java 和 C++ ,代碼重用是經過類的繼承和多態或者語言聲稱的相似機制實現的。對 Python 而言,儘管可使用子類和基於類的多態,事實上在地道的 Python 程序中這些功能極少使用。

經過模塊和函數實現代碼重用更爲廣泛,經過鴨子類型實現動態調度更爲常見。若是發現本身經過超類實現代碼重用,停下來,從新思考。若是發現本身大量使用多態,考慮一下是否用 Python 的 dunder 協議或者鴨子類型策略會更好。

看一下另外一個不錯的 Python 演講,一位 Python 核心貢獻者的 「不要再寫類了」。演講者建議,若是構建的類只有一個命名像一個類的方法(好比 Runnable.run()),那麼實際上只是用函數模擬了一個類,這時候應該停下來。由於在 Python 中,函數是「最高級的」類型,沒有理由這樣作。

Mixin 有時也沒問題

可使用 Mixin 實現基於類的代碼重用,同時不須要走極端使用類型層次。可是不要濫用。「扁平勝於嵌套」也適用於類型層次,因此應該避免僅僅爲了分解行爲而引入沒必要要的必須層次的一層。

Mixin 實際上不是 Python 的特性,多虧了 Python 支持多重繼承。能夠建立基類將功能「注入」到子類中,而沒必要構成類型層次的「重要」組成部分,只須要將基類列入 bases列表中的第一個元素。

要考慮順序,同時不妨記住:bases 自底向上構成層次結構。這裏可讀性的好處在於關於這個類所須要知道的一切都包含在定義自己:「它混入了權限行爲,是專門定製的 Tornado RequestHandler。」

當心框架

Python 有大量的 web,數據庫等框架。Python 語言的一大樂趣在於建立自定義框架至關簡單。使用開源框架時,應該注意不要將本身的「核心代碼」和框架自己結合得過於緊密。

考慮爲本身的代碼建立框架的時候應當慎之又慎。標準庫有不少內置的東西,PyPI 有的就更多了,並且一般你不會須要它

尊重元編程

Python 經過一些特性來支持 「元編程」,包括修飾器,上下文管理器,描述符,import 鉤子,元類和抽象語法樹(AST)轉換。

應該可以自如地使用並理解這些特性,做爲 Python 的核心組成部分這些特性有着充分地支持。可是應當意識到使用這些特性之時,也引入了複雜的失敗場景。因此,要把爲本身的代碼建立元編程工具與決定「建立自定義框架」同等對待。它們意味着同一件事情。真要這麼作的時候,把元編程工具寫成獨立的模塊,寫好文檔!

不要懼怕 「雙下劃線」方法

許多人將 Python 的元編程工具和其對 「雙下劃線」或「dunder」方法(好比 __getattr__)的支持混爲一談。

正如博文 ——「Python 雙下劃線,雙倍驚喜」—— 所言,雙下劃線沒有什麼「特殊的」。它們只不過是 Python 核心開發人員爲全部的 Python 內部協議所起的輕量級命名空間。畢竟,__init__ 也是雙下劃線,沒有什麼神奇的。

的確,有些雙下劃線比其餘的會致使更多使人困惑的結果,好比,沒有很好的理由就重載操做符一般不是什麼好主意。可是它們中也有許多,好比 __repr____str____len__ 以及 __call__ 是 Python 語言的完整組成部分,應該在地道的 Python 代碼中充分利用。不要回避!

代碼風格小禪理

做爲一位核心 Python 開發者,Barry Warsaw 曾經說過「Python 之禪」(PEP 20)被用做 Python 代碼風格指南使他沮喪,由於這本是爲 Python 的內部設計所做的一首小詩。也就是語言的設計以及語言的實現自己。不過必須認可,PEP 20 中有些行能夠看成至關不錯的地道 Python 代碼指南,因此咱們就把它加上了。

美勝於醜

這一條有些主觀,實際上等同於問:接手代碼的人會被折服仍是感到失望?若是接手的人就是三年後的你呢?

顯勝於隱

有時爲了重構以去除重複的代碼,會有一點抽象。應該可以將代碼翻譯成顯現的英語而且大體瞭解它是幹什麼的。不該該有太多的「神奇之處」。

扁平勝於嵌套

這一條很好理解。最好的函數沒有嵌套,既不用循環也不用 if 語句。第二好的函數只有一層嵌套。若是有兩層及以上的嵌套,最好重構成更小的函數。

一樣,不要懼怕將嵌套的 if 語句重構爲多部分佈爾條件。好比:

最好寫成:

 

可讀性確實重要

不要懼怕用 # 添加行註釋。也不要濫用或者寫過多文檔。一點點逐行解釋,一般頗有幫助。不要懼怕使用稍微長一些的名字,由於描述性更好。將 「response」寫成「rsp」沒有任何好處。使用 doctest 風格的例子在文檔字符串中詳細說明邊界狀況。簡潔至上!

錯誤不該被放過

單獨的except: pass 子句危害最大。永遠不要使用。制止全部的異常實在危險。將異常處理限制在一行代碼,而且老是將 except 處理器限制在特定的類型下。除此以外,能夠自如地使用 logging 模塊和 log.exception(…)

若是實現難以解釋,那就是個壞主意

這雖是通用軟件工程原則,可是特別適用於 Python 代碼。大多數 Python 函數和對象均可以有易於解釋的實現。若是難以解釋,極可能是一個壞主意。一般能夠經過「分而治之」將一個難以解釋的函數重寫成易於解釋的函數,也就是分割成多個函數。

測試是個好主意

好吧,咱們篡改了「Python 之禪」中的這一行,原文中「命名空間」纔是個絕妙的好主意。

不過說正經的,優雅卻沒有測試的代碼簡直比哪怕是最醜陋卻測試過的代碼還要差勁。至少醜陋的代碼能夠重構成優雅的,可是優雅的代碼卻不能重構爲能夠證實是正確的代碼,至少不寫測試是作不到的!因此,寫測試吧!拜託!

勢均力敵

咱們把寧願不去解決的爭論放在這個部分。不要由於這些重寫別人的代碼。這裏的東西能夠自由地交替使用。

str.format 仍是重載格式化操做符 %

str.format 更健壯,然而 % 使用 "%s %s" printf 風格的字符串更加簡潔。二者會永遠共存。

若是須要保存 unicode,記得在格式模板中使用 unicode 字符串:

若是最後選擇 %,應該考慮 "%(name)s" 語法,從而可使用字典而不是元組,好比:

此外,不要從新發明輪子。str.format 有一點毫無疑問比 % 更好,那就是支持各類格式化模式,好比人性化的數字和百分數。直接用。

可是選擇哪個都沒有問題。咱們沒有強制規定。

if item 仍是 if item is not None

本條和以前的對於 None 是用 == 仍是 is 沒有關係。這裏咱們實際上利用了 Python 的 「真實性規則」來處理 if item,這其實是「item 不是 None 或者空字符串」的簡寫。

Python 中的真實性有些複雜。顯然第二種寫法對於某些錯誤而言更安全。可是第一種寫法在 Python 代碼中很是常見,並且更短。對此咱們並無強制規定。

隱式多行字符串仍是三引號 """

Python 編譯器在語法分析時,若是多個字符串之間沒有東西,會自動將其拼接爲一個字符串。好比:

大體上等價於:

第一種寫法能夠保持縮進整潔,可是須要醜陋的換行符,第二種寫法不須要換行符,可是打破了縮進。咱們沒有強制規定哪一種更好。

在類仍是實例上使用 raise

事實上給 raise 語句傳入異常或者異常實例均可以。好比,下面兩行代碼大體等價:

本質上,Python 會將第一種寫法自動轉換爲第二種。或許第二個寫法更好,即便沒有其餘緣由,它也能實際上提供一個有用的理由,就像有條有用的消息來解釋爲何 ValueError會發生同樣。可是這兩種寫法等價的,不該該爲這一點就重寫代碼。咱們不作強制規定。

標準工具和項目結構

咱們選擇了一些「最佳組合」工具,以及像樣的 Python 項目會用到的最小初始結構。

標準庫

  • import datetime as dt: 永遠像這樣導入 datetime
  • dt.datetime.utcnow(): 優於 .now(), 後者使用的是當地時間
  • import json: 數據交換的標準
  • from collections import namedtuple: 用來作輕量級數據類型
  • from collections import defaultdict: 用來計數/分組
  • from collections import deque: 快速的雙向隊列
  • from itertools import groupby, chain: 爲了聲明式風格
  • from functools import wraps: 用來編寫合乎規範的裝飾器
  • argparse: 爲了構建「健壯的」命令行工具
  • fileinput: 用來快速構建易於和 UNIX 管道結合使用的工具
  • log = logging.getLogger(__name__): 足夠好用的日誌
  • from __future__ import absolute_import: 修復導入別名

常見第三方庫

  • python-dateutil 用來解析時間和日曆
  • pytz 用來處理時區
  • tldextract 爲了更好地處理 URL
  • msgpack-python 比 JSON 更加緊湊地編碼
  • futures 爲了 Future/pool 併發原語
  • docopt 用來快速編寫一次性命令行工具
  • py.test 用來作單元測試,與 mock 和 hypothesis 結合使用

本地開發項目框架

對全部的 Python 包和庫而言:

  • 根目錄下不要有 __init__.py:目錄名用做包名!
  • mypackage/__init__.py 優於 src/mypackage/__init__.py
  • mypackage/lib/__init__.py 優於 lib/__init__.py
  • mypackage/settings.py 優於 settings.py
  • README.rst 用來給新手描述本項目;使用 rst
  • setup.py 用來構建簡單工具,好比 setup.py develop
  • requirements.txt 是爲 pip 準備的包依賴環境
  • dev-requirements.txt 是爲 tests/local 準備的額外的依賴環境
  • Makefile 用來簡化 (!!!) build/lint/test/run 步驟

另外,永遠記得詳細說明包依賴環境

靈感來源

下面這些連接或許能夠給你啓發,有助於書寫具備良好風格和品味的 Python 代碼。

出發吧,寫更具 Python 風格的代碼!

 

撰稿人

  • Andrew Montalenti (@amontalenti): 原做者
  • Vincent Driessen (@nvie): 編輯並提出意見
相關文章
相關標籤/搜索