個人總結——全面的『Python編碼規範』

浮生若夢的程式設計發表於2018-05-18

本文目的

『動態型別一時爽,程式碼重構火葬場』,說的是:動態語言在初期開發比較爽,但是到後期維護起來比較困難。Python 作為動態語言之一,自然也會有這樣的缺點。其實說『火葬場』,也沒有那麼嚴重,只要嚴格的遵守一組規範,也能做到『重構的時候,也一樣爽』。

不以規矩不成方圓,規範自然是十分重要的,而在動態語言中,尤其重要(很多人拿Python寫指令碼,基本是隨心所欲地寫,自然後期維護困難)。所謂『兵馬未動糧草先行』,我們應該在寫程式碼前,就做好充足的 “表面功夫”。

本文不涉及哪些

不做『文件復讀機』。PEP 8中已經有了的,就不重複了,複述一些“低階(大家都知道了的)” 內容,沒啥意思。

不迷信權威,這裡指『Google Python Guide』,那是適合Google的規範,並不是社群規範,其實我覺得這份規範既不完整,同時,淨是一些『眾人皆知』的內容,並不推薦之。

不搞宗教信仰,奉承實用主義。比如典型的『import this』,只有我一個人覺得不過是一堆空洞的廢話嗎,況且 Python 標準庫中很多地方,也沒有做到『import this』 中的『simple, explicit, and powerful』。每次各種文章(無節操的營銷文章,以各種培訓機構為主體)提及這個東西,我真是渾身不自在&尷尬(寫程式碼是很工程很嚴肅的事情,搞這種玄學幹嘛呢)。

適用範圍 & 原則

  • Python 2.7 - Python 3.x 。雖然官方宣佈了 Python 2的壽命是2020,而且似乎現在 Python 3已經是主流了。但是同學,legacy code 可不是說去掉就去掉的,Python 2仍然會存在相當長一段時間。況且,Python 3 有相對於 Python 2 的 『killer feature』 嗎,並沒有。
  • 以 PEP 8 為藍本,緊緊團結在 PEP 8 周圍。任何非官方的文件,只是參考之(同理,本文亦是某種參考資料)。
  • PEP 8 已經有了的,就不要重複了。

規範

【強制 + 強制】【挑選『靜態檢查』工具,並自始至終都嚴格使用】

簡單來說,就是:
1. Pylint
2. Flake8
3. pytest

一開始就要使用,並且從嚴使用(發點時間瞭解這幾個工具,帶來的收益是無限的,如果你是比較正式的專案的話)。
複製程式碼

【強制+】【多寫UT】

其他程式語言,同理。

有一份UT在手,重構起來,心裡放心很多。

Python的話,只需要瞭解unittest就夠了,pytest也可以。
複製程式碼

【強制】【檔案編碼 & Unicode】

PS:下面這幾條,能幫你避免很多無聊的編碼解碼問題,所以我覺得很重要

  1. 使用 4 空格縮排,禁用任何 TAB 符號
  2. 原始碼檔案使用 UTF-8 無 BOM 編碼格式
  3. 總是使用 Unix \n 風格換行符
  4. 在每一個 py 檔案頭,都新增如下內容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
 

# 只匯入 future 空間的這兩個特性就夠了,其他特性容易造成其他方面的『不相容』,沒有使用的必要性。
from __future__ import (absolute_import, unicode_literals)

所以,你需要了解 editorconfig 這個東西,從根源上統一規範,不符合規範的,直接拒絕 PUSH or MERGE
複製程式碼

###【強制】【命名】

  1. class,function 該如何命名,不贅述,嚴格照著PEP8做就行了,不用多想。
  2. 全域性變數(全域性變數,一般是常量,我們認為:凡是全域性的,都是常量),應該始終使用全大寫,如:
GLOBAL_PUBLIC = "G1"
_GLOBAL_PRIVATE = "G2"

class Person:
    _GLOBAL_IN_CLASS = 'G3'
    

按照這條要求,其實很多庫or開源庫,都是不符合要求的。為什麼這麼強硬呢?
Python中的變數定義,是不分『宣告』、『定義』、『初始化』、『賦值』這幾個概念的,所以一個
a = 1
如果沒有上下文,你是很難確定其作用域的,也很難確定 這到底是初始化還是賦值(a已經存在過),
如果全域性變數還不用全大寫,帶來的麻煩只會更多。

如果始終堅持這個原則,將會給程式碼的可讀性帶來極大提升。
複製程式碼

【強制】定義列舉,始終加 Enum字尾;定義異常始終加Exception字尾;定義mixin,始終加Mixin字尾,如

class DirectionEnum:
    UP = 1
    DOWN = 2
    
    
class MyException(Exception):
    pass
class MyError(Exception):
    pass
    
class SomeMixin:
    pass
複製程式碼

【強制】【強化private的概念】

即:最小知識原則,對外暴露的東西越少越好

翻譯成大白話就是:
1. 例項屬性,一般定義成private的
2. class,對外提供的方法越少越好
3. module,對外提供的介面越少越好
4. package,對外提供的 module 越少越好

翻譯成程式碼就是:
1. 專案佈局
package/
    __init__.py
    _private_mod.py
    public_mod.py    
    
2. 某模組內容
public_mod.py
PUBLIC_GLOBAL = 'G1'
_PRIVATE_GLOBAL = 'G2'
class _Class:
    pass
class PublicClass:
    _PRIVATE_GLOBAL = 'G3'
    
    def __init__(self, name,age):
        self._name = name
        self._age = age
    def public_method(self):
        pass
    def _private(self):
        pass
        

所有東西,一開始就要定義成私有的,等到確實需要開放訪問了,才開放出去。
複製程式碼

【強制&重要】【關注公開介面的複雜性】

最好的介面是這樣的,呼叫者無腦使用
def interface():
    pass
    
次等介面是這樣的
def interface(param1):
    pass
    
次次等介面是這樣的
def interface(p1, p2):
    pass

最大忍受限度的介面是這樣的
def interface(p1, p2, p3='SOME DEFAULT'):
    pass
def interface(p1, *args):
    pass
    
不可接受的介面是這樣的
def interface(p1, p2, **kwargs):
    pass

令人無語的介面是這樣的
def interface(*args, **kwargs):  
# 儘量不要使用 **kwargs, 某些流行庫有這樣的毛病,我是覺得:極大地增加了呼叫者的心理負擔,反映了介面設計者的懶惰
    pass
    
一直覺得,**kwargs只適用於極少數明確的場合,並且需要輔以很明確的文件說明(解釋為什麼要使用),然而現實是,這個
特性已經被大家濫用了,有必要單獨說明之。
複製程式碼

PS:我一直覺得,濫用 **kwargs 的API,幾乎都不是好 API,無形增加心理負擔。

【推薦】【以package去設計名稱空間,而不是基於module】

【推薦】【瞭解如下內容】

__init__.py 的作用

__main__.py 的作用

if __name__ == '__main__': 的作用

Python的名稱空間載入機制,即:sys.path sys.modules 的內容
複製程式碼

【推薦】【合理設計專案目錄結構】

如果是使用某種框架(如Django),那麼按照框架的規範來;如果是“非框架”專案,則按照如下結構

project
    project/
        __init__.py
        core/
        utils/
        constants/
        
        
        __main__.py
        
    tests/
    docs/
    examples/
    README.md
    .pylintrc
    .flake8
    
複製程式碼

相關文章