天天看點

PEP8 編碼風格

文章目錄

    • PEP8 編碼風格
      • 引言
      • 代碼布局(Code Lay-Out)
        • 縮進(Indentation)
        • 每行最大長度(Maximum Line Length)
        • 二進制運算符之前還是之後換行 ?(Should a line break before or after a binary operator?)
        • 空行(Blank Line)
        • 源檔案編碼
      • 子產品引用
      • 子產品級的雙下劃線命名(Module level funder names)
      • 字元串引用(String Quotes)
      • 表達式和語句中的空格(Whitespace In Expressions And Statements)
      • 注釋(Comments)
      • 塊注釋(Block Comments)
      • 文檔字元串(Documentation Strings)
    • 命名約定(Naming Conventions)
      • 首要原則(Overriding Principle)
      • 描述:命名風格(Descriptive: Naming Styles)
      • 規範:命名約定(Prescriptive: Naming Conventions)
        • 需要避免的命名(Names To Avoid)
        • ASCII相容性(ASCII Compatibility)
        • 包和子產品命名(Package And Module Names)
        • 類命名(Class Names)
        • 類型變量命名(Type variable names)
        • 異常命名(Exception Names)
        • 全局變量命名(Global Variable Names)
        • 函數命名(Function Names)
        • 方法命名和執行個體變量(Method Names And Instance Variables)
        • 常量(Constants)
        • 繼承的設計(Designing For Inheritance)
    • 程式設計建議(Programming Recommendations)

PEP8 編碼風格

引言

A Follish Consistency Is The Hobgoblin Of Little Minds

保持盲目的一緻時頭腦簡單的表現

Guido 的一個重要觀點就是代碼被讀的次數遠多于被寫的次數

代碼布局(Code Lay-Out)

縮進(Indentation)

每個縮進級别采用 4 個空格

連續行的縮進:

  • Python 隐式續行,即垂直對齊于圓括号、花括号和方括号
  • 懸挂縮進(hanging indent),第一行不應該包括參數,并且在續行中需要再縮進一個級别

每行最大長度(Maximum Line Length)

将所有行都限制在 79 個字元長度以内

確定續行的縮進适當

二進制運算符之前還是之後換行 ?(Should a line break before or after a binary operator?)

于二進制運算符之前換行

income = (gross_wages
         	+ tax_interest
         	+ ...)
           

空行(Blank Line)

建議使用 2 個空行分隔最外層的函數(function)和類(class)定義

使用 1 個空行來分隔類中的方法(method)定義

在函數内可以使用适量的空行來分隔代碼邏輯

源檔案編碼

Python 核心發行版中的代碼應該一直使用 UTF-8

子產品引用

Imports 應該分行寫

import os
import sys
           

Imports 應該按照下面的順序分組來寫

  1. 标準庫 imports
  2. 相關第三方庫 imports
  3. 本地應用/庫的特定 imports

不同組的 imports 之前用空格隔開

  • 推薦使用絕對 imports,因為這樣通常更加易讀
    import mypkg import sibling
    from mypkg.sibling import example
               

标準代碼庫應當一直使用絕對 imports,避免複雜的包布局

from foo.bar.yourclass import YourClass
           
  • 避免使用通配符 imports,from import *

子產品級的雙下劃線命名(Module level funder names)

子產品級的“雙下劃線”(變量名以兩個下劃線開頭,兩個下劃線結尾)變量,比如

__all__

,

__author

Python 要求子產品中

__future__

的導入必須出現在除文檔字元串(docstring)之外的任何其它代碼之前

"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys
           

字元串引用(String Quotes)

  • 對于單引号和雙引号,最好選擇一種使用,避免混淆。
  • 當使用三引号表示的字元串,使用雙引号字元來表示,這樣可以和PEP 257 的文檔字元串規則保持一緻
    def func():
      """describe the func of this def"""
      print('test')
               

表達式和語句中的空格(Whitespace In Expressions And Statements)

  • 方括号,圓括号和花括号之後:
    #正确的例子:
    spam(ham[1], {eggs: 2})
    
    #錯誤的例子:
    spam( ham[ 1 ], { eggs: 2 } )
               
  • 逗号,分号或冒号之前:
    #正确的例子:
    if x == 4: print x, y; x, y = y, x
    
    #錯誤的例子:
    if x == 4 : print x , y ; x , y = y , x
               
  • 不過,在切片操作時,冒号和二進制運算符是一樣的,應該在其左右兩邊保留相同數量的空格(就像對待優先級最低的運算符一樣)。在擴充切片操作中,所有冒号的左右兩邊空格數都應該相等。不過也有例外,當切片操作中的參數被省略時,應該也忽略空格。
  • 避免任何行末的空格
  • 在二進制運算符的兩邊都使用一個空格:指派運算符(

    =

    ),增量指派運算符(

    +=

    ,

    -=

    etc.),比較運算符(

    ==

    ,

    <

    ,

    >

    ,

    !=

    ,

    <>

    ,

    <=

    ,

    >=

    ,

    in

    ,

    not in

    ,

    is

    ,

    is not

    ),布爾運算符(

    and

    ,

    or

    ,

    not

    )。
  • 如果使用了優先級不同的運算符,則在優先級較低的操作符周圍增加空白。請你自行判斷,不過永遠不要用超過1個空格,永遠保持二進制運算符兩側的空白數量一樣。
  • #正确的例子:
    i = i + 1
    submitted += 1
    x = x*2 - 1
    hypot2 = x*x + y*y
    c = (a+b) * (a-b)
    
    #錯誤的例子:
    i=i+1
    submitted +=1
    x = x * 2 - 1
    hypot2 = x * x + y * y
    c = (a + b) * (a - b)
               
  • 使用

    =

    表示關鍵字參數或參數預設值時,不要在其周圍使用空格。
    def complex(real, imag=0.0):
      return magic(r=real, i=imag)
               
  • 函數注解中的:也遵循:加空格的規則,在 -> 兩側各加一個空格
# true
def munge(sep: AnyStr = None): ...
  
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
           
  • 有時也可以将短小的if/for/while中的語句寫在一行,但對于有多個分句的語句永遠不要這樣做。也要避免将多行都寫在一起。
    #最好不要這樣:
    if foo == 'blah': do_blah_thing()
    for x in lst: total += x
    while t < 10: t = delay()
    
    #絕對不要這樣:
    if foo == 'blah': do_blah_thing()
    else: do_non_blah_thing()
    
    try: something()
    finally: cleanup()
    
    do_one(); do_two(); do_three(long, argument,
    list, like, this)
    
    if foo == 'blah': one(); two(); three()
               
  • 何時在末尾加逗号(When to use trailing commas)

末尾逗号通常是可選的,除非在定義單元素元組(tuple)時是必需的(而且在Python 2中,它們具有print語句的語義)。為了清楚起見,建議使用括号(技術上來說是備援的)括起來。

#正确的例子:
FILES = ('setup.cfg',)

#也正确,但令人困惑:
FILES = 'setup.cfg',
           

當使用版本控制系統時,在将來有可能擴充的清單末尾添加備援的逗号是有好處的。具體的做法是将每一個元素寫在單獨的一行,并在行尾添加逗号,右括号單獨占一行。但是,與有括号在同一行的末尾元素後面加逗号是沒有意義的(上述的單元素元組除外)。

#正确的例子:
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
initialize(FILES,
           error=True,
           )

#錯誤的例子:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
           

注釋(Comments)

  • 和代碼沖突的注釋還不如沒有
  • 當有代碼改動時,一定優先更改注釋使其保持最新
  • 注釋應該是完整的多個句子。如果注釋是一個短句或一個句子,其首字母應該大寫,除非開頭是一個以小寫字母開頭的辨別符
  • 使用英文寫作
  • 如果注釋很短,結束的句号可以被忽略。且在句尾的句号後再加兩個空格

塊注釋(Block Comments)

塊注釋一般寫在對應代碼之前,并且和對應代碼有同樣的縮進級别。

文檔字元串(Documentation Strings)

要知道如何寫出好的文檔字元串,請參考 PEP 257

  • 所有的公共子產品,函數,類和方法都應該有文檔字元串。
  • 對于非公共方法,文檔字元串不是必要的,但是你應該留有注釋說明該方法的功能,該注釋應當出現在 def 的下一行
  • PEP 257 描述了好的文檔字元應該遵循的規則。其中,最重要的是,多行文檔字元串以單行 “”" 結尾,不能有其它字元:
    """Return a foobang
    
    Optional plotz says to frobnicate the bizbaz first.
    """
               
  • 對于僅有一行的文檔字元串,結尾處的 “”" 應該也寫在這一行

命名約定(Naming Conventions)

Python标準庫的命名約定有一些混亂,是以我們永遠都無法保持一緻。但如今仍然存在一些推薦的命名标準。新的子產品和包(包括第三方架構)應該采用這些标準,但若是已經存在的包有另一套風格的話,還是應當與原有的風格保持内部一緻。

首要原則(Overriding Principle)

對于使用者可見的公共部分API,其命名應當表達出功能用途而不是其具體的實作細節。

描述:命名風格(Descriptive: Naming Styles)

存在很多不同的命名風格,最好能夠獨立地從命名對象的用途認出采用了哪種命名風格。

通常區分以下命名樣式:

  • b

    (單個小寫字母)
  • B

    (單個大寫字母)
  • lowercase

    (小寫)
  • lower_case_with_underscores

    (帶下劃線小寫)
  • UPPERCASE

    (大寫)
  • UPPER_CASE_WITH_UNDERSCORES

    (帶下劃線大寫)
  • CapitalizedWords

    (也叫做CapWords或者CamelCase – 因為單詞首字母大寫看起來很像駝峰)。也被稱作StudlyCaps。

    注意:當CapWords裡包含縮寫時,将縮寫部分的字母都大寫。

    HTTPServerError

    HttpServerError

    要好。
  • mixedCase

    (注意:和CapitalizedWords不同在于其首字母小寫!)
  • Capitalized_Words_With_Underscores

    (這種風格超醜!)

也有風格使用簡短唯一的字首來表示一組相關的命名。這在Python中并不常見,但為了完整起見這裡也捎帶提一下。比如,

os.stat()

函數傳回一個tuple,其中的元素名原本為

st_mode

,

st-size

,

st_mtime

等等。(這樣做是為了強調和POSIX系統調用結構之間的關系,可以讓程式員更熟悉。)

X11庫中的公共函數名都以X開頭。在Python中這樣的風格一般被認為是不必要的,因為屬性和方法名之前已經有了對象名的字首,而函數名前也有了子產品名的字首。

此外,要差別以下劃線開始或結尾的特殊形式(可以和其它的規則結合起來):

  • _single_leading_underscore

    : 以單個下劃線開頭是”内部使用”的弱标志。 比如,

    from M import *

    不會import下劃線開頭的對象。
  • single_trailing_underscore_

    : 以單個下劃線結尾用來避免和Python關鍵詞産生沖突,例如:
    Tkinter.Toplevel(master, class_='ClassName')
               
  • __double_leading_underscore

    : 以雙下劃線開頭的風格命名類屬性表示觸發命名修飾(在FooBar類中,

    __boo

    命名會被修飾成

    _FooBar__boo

    ; 見下)。
  • __double_leading_and_trailing_underscore__

    : 以雙下劃線開頭和結尾的命名風格表示“魔術”對象或屬性,存在于使用者控制的命名空間(user-controlled namespaces)裡(也就是說,這些命名已經存在,但通常需要使用者覆寫以實作使用者所需要的功能)。 比如,

    __init__

    ,

    __import__

    __file__

    。請依照文檔描述來使用這些命名,千萬不要自己發明。

規範:命名約定(Prescriptive: Naming Conventions)

需要避免的命名(Names To Avoid)

不要使用字元’l’(L的小寫的字母),’O’(o大寫的字母),或者’I’(i的大寫的字母)來作為單個字元的變量名。

在一些字型中,這些字元和數字1和0無法差別開來。比如,當想使用’l’時,使用’L’代替。

ASCII相容性(ASCII Compatibility)

标準庫中使用的辨別符必須與ASCII相容(參見PEP 3131中的policy這一節) 。

包和子產品命名(Package And Module Names)

子產品命名應短小,且為全小寫。若下劃線能提高可讀性,也可以在子產品名中使用。

Python 包命名也應該短小,且為全小寫,但不應該使用下劃線

類命名(Class Names)

類命名應該使用駝峰命名法

當接口已有文檔說明且主要是被用作調用時,也可以使用函數的命名約定。

注意對于内建命名(builtin names)有一個特殊的約定:大部分内建名都是一個單詞(或者兩個一起使用的單詞),駝峰(CapWords)的約定隻對異常命名和内建常量使用。

類型變量命名(Type variable names)

PEP 484中引入的類型變量名稱通常應使用簡短的駝峰命名:

T

AnyStr

Num

。 建議将字尾

_co

_contra

添加到用于聲明相應的協變(covariant)和逆變(contravariant)的行為。例如:

from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
           

異常命名(Exception Names)

由于異常實際上也是類,是以類命名約定也适用與異常。不同的是,如果異常實際上是抛出錯誤的話,異常名前應該加上”Error”的字首。

全局變量命名(Global Variable Names)

(在此之前,我們先假定這些變量都僅在同一個子產品内使用。)這些約定同樣也适用于函數命名。

對于引用方式設計為

from M import *

的子產品,應該使用

__all__

機制來避免import全局變量,或者采用下劃線字首的舊約定來命名全局變量,進而表明這些變量是“子產品非公開的”。

函數命名(Function Names)

函數命名應該都是小寫,必要時使用下劃線來提高可讀性。

隻有當已有代碼風格已經是混合大小寫時(比如threading.py),為了保留向後相容性才使用混合大小寫。

方法命名和執行個體變量(Method Names And Instance Variables)

使用函數命名的規則:小寫單詞,必要時使用下劃線分開以提高可讀性。

僅對于非公開方法和變量命名在開頭使用一個下劃線。

避免和子類的命名沖突,使用兩個下劃線開頭來觸發Python的命名修飾機制。

Python類名的命名修飾規則:如果類Foo有一個屬性叫

__a

,不能使用

Foo.__a

的方式通路該變量。(有使用者可能仍然堅持使用

Foo._Foo__a

的方法通路。)一般來說,兩個下劃線開頭的命名方法僅用于避免與設計為子類的類中的屬性名沖突。

注意:關于__names的使用也有一些争論(見下)。

常量(Constants)

常量通常是在子產品級别定義的,使用全部大寫并用下劃線将單詞分開。如:

MAX_OVERFLOW

TOTAL

繼承的設計(Designing For Inheritance)

記得永遠差別類的方法和執行個體變量(屬性)應該是公開的還是非公開的。如果有疑慮的話,請選擇非公開的;因為之後将非公開屬性變為公開屬性要容易些。

公開屬性是那些你希望和你定義的類無關的客戶來使用的,并且確定不會出現向後不相容的問題。非公開屬性是那些不希望被第三方使用的部分,你可以不用保證非公開屬性不會變化或被移除。

我們在這裡沒有使用“私有(private)”這個詞,因為在Python裡沒有什麼屬性是真正私有的(這樣設計省略了大量不必要的工作)。

另一類屬性屬于子類API的一部分(在其他語言中經常被稱為”protected”)。一些類是為繼承設計的,要麼擴充要麼修改類的部分行為。當設計這樣的類時,需要謹慎明确地決定哪些屬性是公開的,哪些屬于子類API,哪些真的隻會被你的基類調用。

請記住以上幾點,下面是Python風格的指南:

  • 公開屬性不應該有開頭下劃線。
  • 如果公開屬性的名字和保留關鍵字有沖突,在你的屬性名尾部加上一個下劃線。這比采用縮寫和簡寫更好。(然而,和這條規則沖突的是,‘cls’對任何變量和參數來說都是一個更好地拼寫,因為大家都知道這表示class,特别是在類方法的第一個參數裡。)

    注意 1:對于類方法,參考之前的參數命名建議。

  • 對于簡單的公共資料屬性,最後僅公開屬性名字,不要公開複雜的調用或設值方法。請記住,如果你發現一個簡單的資料屬性需要增加功能行為時,Python為功能增強提供了一個簡單的途徑。這種情況下,使用

    Properties

    注解将功能實作隐藏在簡單資料屬性通路文法之後。

    注意 1:

    Properties

    注解僅僅對新風格類有用。

    注意 2:盡量保證功能行為沒有副作用,盡管緩存這種副作用看上去并沒有什麼大問題。

    注意 3: 對計算量大的運算避免試用properties;屬性的注解會讓調用者相信通路的運算量是相對較小的。

  • 如果你的類将被子類繼承的話,你有一些屬性并不想讓子類通路,考慮将他們命名為兩個下劃線開頭并且結尾處沒有下劃線。這樣會觸發Python命名修飾算法,類名會被修飾添加到屬性名中。這樣可以避免屬性命名沖突,以免子類會不經意間包含相同的命名。

    注意 1:注意命名修飾僅僅是簡單地将類名加入到修飾名中,是以如果子類有相同的類名合屬性名,你可能仍然會遇到命名沖突問題。

    注意 2:命名修飾可以有特定用途,比如在調試時,

    __getattr__()

    比較不友善。然而命名修飾算法的可以很好地記錄,并且容意手動執行。

    注意 3:不是所有人都喜歡命名修飾。需要試着去平衡避免偶然命名沖突的需求和進階調用者使用的潛在可能性。

程式設計建議(Programming Recommendations)

  • 代碼應該以不影響其他Python實作(PyPy,Jython,IronPython,Cython,Psyco等)的方式編寫。

    例如,不要依賴于 CPython 在字元串拼接時的優化實作,像這種語句形式

    a += b

    a = a + b

    。即使是 CPython(僅對某些類型起作用) 這種優化也是脆弱的,不是在所有的實作中都不使用引用計數。在庫中性能敏感的部分,用

    ''.join

    形式來代替。這會確定在所有不同的實作中字元串拼接是線性時間的。
  • 與單例作比較,像

    None

    應該用

    is

    is not

    ,從不使用

    ==

    操作符。

    同樣的,當心

    if x is not None

    這樣的寫法,你是不知真的要判斷

    x

    不是

    None

    。例如,測試一個預設值為

    None

    的變量或參數是否設定成了其它值,其它值有可能是某種特殊類型(如容器),這種特殊類型在邏輯運算時其值會被當作

    Flase

    來看待。
  • is not

    操作符而不是

    not ... is

    。雖然這兩個表達式是功能相同的,前一個是更可讀的,是首選。
  • 捕獲異常時,盡可能使用明确的異常,而不是用一個空的

    except:

    語句

    例如,用:

    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
               
  • 另外,對于所有

    try

    /

    except

    子句,将

    try

    子句限制為必需的絕對最小代碼量。同樣,這樣可以避免屏蔽錯誤。

    推薦的寫法:

    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
               
    不推薦的寫法:
    try:
        # Too broad!
        return handle_value(collection[key])
    except KeyError:
        # Will also catch KeyError raised by handle_value()
        return key_not_found(key)
               
  • 當某個資源僅被特定代碼段使用,用

    with

    語句確定其在使用後被立即幹淨的清除了,

    try/finally

    也是也接受的。
  • 當它們做一些除了擷取和釋放資源之外的事的時候,上下文管理器應該通過單獨的函數或方法調用。例如:

    推薦的寫法:

    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
               
    不推薦的寫法:
    with conn:
        do_stuff_in_transaction(conn)
               
  • 堅持使用

    return

    語句。函數内的

    return

    語句都應該傳回一個表達式,或者

    None

    。如果一個

    return

    語句傳回一個表達式,另一個沒有傳回值的應該用

    return None

    清晰的說明,并且在一個函數的結尾應該明确使用一個

    return

    語句(如果有傳回值的話)。

    推薦的寫法:

    def foo(x):
        if x >= 0:
            return math.sqrt(x)
        else:
            return None
    
    def bar(x):
        if x < 0:
            return None
        return math.sqrt(x)
               
    不推薦的寫法:
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
    
    def bar(x):
        if x < 0:
            return
        return math.sqrt(x)
               
  • 用字元串方法代替字元串子產品。

    字元串方法總是快得多,并且與unicode字元串共享相同的API。如果需要與2.0以下的Python的向後相容,則覆寫此規則。

  • ''.startswith()

    ''.endswith()

    代替字元串切片來檢查字首和字尾。

    startswith()

    endswith()

    是更簡潔的,不容易出錯的。例如:
    #推薦的寫法:
    if foo.startswith('bar'):
    
    #不推薦的寫法:
    if foo[:3] == 'bar':
               
  • 對象類型的比較應該始終使用

    isinstance()

    而不是直接比較。
    #推薦的寫法:
    if isinstance(obj, int):
    
    #不推薦的寫法:
    if type(obj) is type(1):
               
    當比較一個對象是不是字元串時,記住它有可能也是一個 unicode 字元串!在 Python 2 裡面,

    str

    unicode

    有一個公共的基類叫

    basestring

    ,是以你可以這樣做:
    if isinstance(obj, basestring):
               
    注意,在 Python 3 裡面,

    unicode

    basestring

    已經不存在了(隻有

    str

    ),

    byte

    對象不再是字元串的一種(被一個整數序列替代)。
  • 不要讓字元串對尾随的空格有依賴。這樣的尾随空格是視覺上無法區分的,一些編輯器(或者,reindent.py)會将其裁剪掉。
  • 不要用

    ==

    比較

    True

    False

    #推薦的寫法:
    if greeting:
    
    #不推薦的寫法:
    if greeting == True:
    
    #更加不推薦的寫法:
    if greeting is True: