天天看點

Python PEP8 中文版-編碼規範Introduction 介紹A Foolish Consistency is the Hobgoblin of Little Minds 盡信書,則不如無書Code lay-out 代碼布局Whitespace in Expressions and Statements 表達式和語句中的空格Comments 注釋Naming Conventions 命名規範Programming Recommendations 程式設計建議參考

pep原文連結:http://legacy.python.org/dev/peps/pep-0008/

Introduction 介紹

A Foolish Consistency is the Hobgoblin of Little Minds 盡信書,則不如無書

Code lay-out 代碼布局

Indentation 縮進

Tabs or Spaces? 制表符還是空格?

Maximum Line Length 行的最大長度

Should a line break before or after a binary operator? 在二進制運算符之前應該換行嗎?

Blank Lines 空行

Source File Encoding 源檔案編碼

Imports 導入

Module level dunder names 子產品級的“呆”名

String Quotes 字元串引号

Whitespace in Expressions and Statements 表達式和語句中的空格

Pet Peeves 不能忍受的事情

Other Recommendations 其他建議

Comments 注釋

Block Comments 塊注釋

Inline Comments 行内注釋

Documentation Strings 文檔字元串

Naming Conventions 命名規範

Overriding Principle 最重要的原則

Descriptive: Naming Styles 描述:命名風格

Prescriptive: Naming Conventions 約定俗成:命名約定

Names to Avoid 應避免的名字

Package and Module Names 包名和子產品名

Class Names 類名

Exception Names 異常名

Global Variable Names 全局變量名

Function Names 函數名

Function and method arguments 函數和方法參數

Method Names and Instance Variables 方法名和執行個體變量

Constants 常量

Designing for inheritance 繼承的設計

Public and internal interfaces 公共和内部的接口

Programming Recommendations 程式設計建議

Function Annotations 功能注釋

參考

Introduction 介紹

本文提供的Python代碼編碼規範基于Python主要發行版本的标準庫。Python的C語言實作的C代碼規範請檢視相應的PEP指南1。

這篇文檔以及PEP 257(文檔字元串的規範)改編自Guido原始的《Python Style Guide》一文,同時添加了一些來自Barry的風格指南2。

這篇規範指南随着時間的推移而逐漸演變,随着語言本身的變化,過去的約定也被淘汰了。

許多項目有自己的編碼規範,在出現規範沖突時,項目自身的規範優先。

A Foolish Consistency is the Hobgoblin of Little Minds 盡信書,則不如無書

Guido的一條重要的見解是代碼閱讀比寫更加頻繁。這裡提供的指導原則主要用于提升代碼的可讀性,使得在大量的Python代碼中保持一緻。就像PEP 20提到的,“Readability counts”。

這是一份關于一緻性的風格指南。這份風格指南的風格一緻性是非常重要的。更重要的是項目的風格一緻性。在一個子產品或函數的風格一緻性是最重要的。

然而,應該知道什麼時候應該不一緻,有時候編碼規範的建議并不适用。當存在模棱兩可的情況時,使用自己的判斷。看看其他的示例再決定哪一種是最好的,不要羞于發問。

特别是不要為了遵守PEP約定而破壞相容性!

幾個很好的理由去忽略特定的規則:

  1. 當遵循這份指南之後代碼的可讀性變差,甚至是遵循PEP規範的人也覺得可讀性差。
  2. 與周圍的代碼保持一緻(也可能出于曆史原因),盡管這也是清理他人混亂(真正的Xtreme Programming風格)的一個機會。
  3. 有問題的代碼出現在發現編碼規範之前,而且也沒有充足的理由去修改他們。
  4. 當代碼需要相容不支援編碼規範建議的老版本Python。

Code lay-out 代碼布局

Indentation 縮進

每一級縮進使用4個空格。

續行應該與其包裹元素對齊,要麼使用圓括号、方括号和花括号内的隐式行連接配接來垂直對齊,要麼使用挂行縮進對齊3。當使用挂行縮進時,應該考慮到第一行不應該有參數,以及使用縮進以區分自己是續行。

推薦:

# 與左括号對齊
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# 用更多的縮進來與其他行區分
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# 挂行縮進應該再換一行
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
           

不推薦:

# 沒有使用垂直對齊時,禁止把參數放在第一行
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# 當縮進沒有與其他行區分時,要增加縮進
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)
           

四空格的規則對于續行是可選的。

可選:

# 挂行縮進不一定要用4個空格
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)
           

當if語句的條件部分長到需要換行寫的時候,注意可以在兩個字元關鍵字的連接配接處(比如if),增加一個空格,再增加一個左括号來創造一個4空格縮進的多行條件。這會與if語句内同樣使用4空格縮進的代碼産生視覺沖突。PEP沒有明确指明要如何區分i發的條件代碼和内嵌代碼。可使用的選項包括但不限于下面幾種情況:

# 沒有額外的縮進
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# 增加一個注釋,在能提供文法高亮的編輯器中可以有一些區分
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    do_something()

# 在條件判斷的語句添加額外的縮進
if (this_is_one_thing
        and that_is_another_thing):
    do_something()
           
  • (可以參考下面關于是否在二進制運算符之前或之後截斷的讨論) 

    在多行結構中的大括号/中括号/小括号的右括号可以與内容對齊單獨起一行作為最後一行的第一個字元,就像這樣:

my_list = [
    1, 2, 3,
    4, 5, 6,
    ]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
    )
           

或者也可以與多行結構的第一行第一個字元對齊,就像這樣:

my_list = [
    1, 2, 3,
    4, 5, 6,
]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)
           

Tabs or Spaces? 制表符還是空格?

空格是首選的縮進方式。 

制表符隻能用于與同樣使用制表符縮進的代碼保持一緻。 

Python3不允許同時使用空格和制表符的縮進。 

混合使用制表符和空格縮進的Python2代碼應該統一轉成空格。 

當在指令行加入-t選項執行Python2時,它會發出關于非法混用制表符與空格的警告。當使用–tt時,這些警告會變成錯誤。強烈建議使用這樣的參數。

Maximum Line Length 行的最大長度

所有行限制的最大字元數為79。 

沒有結構化限制的大塊文本(文檔字元或者注釋),每行的最大字元數限制在72。 

限制編輯器視窗寬度可以使多個檔案并行打開,并且在使用代碼檢查工具(在相鄰列中顯示這兩個版本)時工作得很好。 

大多數工具中的預設封裝破壞了代碼的可視化結構,使代碼更難以了解。避免使用編輯器中預設配置的80視窗寬度,即使工具在幫你折行時在最後一列放了一個标記符。某些基于Web的工具可能根本不提供動态折行。 

一些團隊更喜歡較長的行寬。如果代碼主要由一個團隊維護,那這個問題就能達成一緻,可以把行長度從80增加到100個字元(更有效的做法是将行最大長度增加到99個字元),前提是注釋和文檔字元串依然已72字元折行。 

Python标準庫比較保守,需要将行寬限制在79個字元(文檔/注釋限制在72)。 

較長的代碼行選擇Python在小括号,中括号以及大括号中的隐式續行方式。通過小括号内表達式的換行方式将長串折成多行。這種方式應該優先使用,而不是使用反斜杠續行。 

反斜杠有時依然很有用。比如,比較長的,多個with狀态語句,不能使用隐式續行,是以反斜杠是可以接受的:

with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())
           

(請參閱前面關于多行if-語句的讨論,以獲得關于這種多行with-語句縮進的進一步想法。) 

另一種類似情況是使用assert語句。 

確定在續行進行适當的縮進。

Should a line break before or after a binary operator? 在二進制運算符之前應該換行嗎?

幾十年來,推薦的風格是在二進制運算符之後中斷。但是這回影響可讀性,原因有二:操作符一般分布在螢幕上不同的列中,而且每個運算符被移到了操作數的上一行。下面例子這個情況就需要額外注意,那些變量是相加的,那些變量是相減的:

# 不推薦: 操作符離操作數太遠
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)
           

為了解決這種可讀性的問題,數學家和他們的出版商遵循了相反的約定。Donald Knuth在他的Computers and Typesetting系列中解釋了傳統規則:“盡管段落中的公式總是在二進制運算符和關系之後中斷,顯示出來的公式總是要在二進制運算符之前中斷”4。 

遵循數學的傳統能産出更多可讀性高的代碼:

# 推薦:運算符和操作數很容易進行比對
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)
           

在Python代碼中,允許在二進制運算符之前或之後中斷,隻要本地的約定是一緻的。對于新代碼,建議使用Knuth的樣式。

Blank Lines 空行

頂層函數和類的定義,前後用兩個空行隔開。 

類裡的方法定義用一個空行隔開。 

相關的功能組可以用額外的空行(謹慎使用)隔開。一堆相關的單行代碼之間的空白行可以省略(例如,一組虛拟實作 dummy implementations)。 

在函數中使用空行來區分邏輯段(謹慎使用)。 

Python接受control-L(即^L)換頁符作為空格;許多工具把這些字元當作頁面分隔符,是以你可以在檔案中使用它們來分隔相關段落。請注意,一些編輯器和基于Web的代碼閱讀器可能無法識别control-L為換頁,将在其位置顯示另一個字形。

Source File Encoding 源檔案編碼

Python核心釋出版本中的代碼總是以UTF-8格式編碼(或者在Python2中用ASCII編碼)。 

使用ASCII(在Python2中)或UTF-8(在Python3中)編碼的檔案不應具有編碼聲明。 

在标準庫中,非預設的編碼應該隻用于測試,或者當一個注釋或者文檔字元串需要提及一個包含内ASCII字元編碼的作者名字的時候;否則,使用\x,\u,\U , 或者 \N 進行轉義來包含非ASCII字元。 

對于Python 3和更高版本,标準庫規定了以下政策(參見 PEP 3131):Python标準庫中的所有辨別符必須使用ASCII辨別符,并在可行的情況下使用英語單詞(在許多情況下,縮寫和技術術語是非英語的)。此外,字元串文字和注釋也必須是ASCII。唯一的例外是(a)測試非ASCII特征的測試用例,以及(b)作者的名稱。作者的名字如果不使用拉丁字母拼寫,必須提供一個拉丁字母的音譯。 

鼓勵具有全球閱聽人的開放源碼項目采取類似的政策。

Imports 導入

  • 導入通常在分開的行,例如:
推薦: import os
     import sys

不推薦:  import sys, os
           

但是可以這樣:

from subprocess import Popen, PIPE
           
  • 導入總是位于檔案的頂部,在子產品注釋和文檔字元串之後,在子產品的全局變量與常量之前。 

    導入應該按照以下順序分組:

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

      你應該在每一組導入之間加入空行。

  • 推薦使用絕對路徑導入,如果導入系統沒有正确的配置(比如包裡的一個目錄在sys.path裡的路徑後),使用絕對路徑會更加可讀并且性能更好(至少能提供更好的錯誤資訊):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
           
  • 然而,顯示的指定相對導入路徑是使用絕對路徑的一個可接受的替代方案,特别是在處理使用絕對路徑導入不必要冗長的複雜包布局時:
from . import sibling
from .sibling import example
           

标準庫要避免使用複雜的包引入結構,而總是使用絕對路徑。 

不應該使用隐式相對路徑導入,并且在Python 3中删除了它。

  • 當從一個包含類的子產品中導入類時,常常這麼寫:
from myclass import MyClass
from foo.bar.yourclass import YourClass
           

如果上述的寫法導緻名字的沖突,那麼這麼寫:

import myclass
import foo.bar.yourclass
           

然後使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”。

  • 避免通配符的導入(from import *),因為這樣做會不知道命名空間中存在哪些名字,會使得讀取接口和許多自動化工具之間産生混淆。對于通配符的導入,有一個防禦性的做法,即将内部接口重新釋出為公共API的一部分(例如,用可選加速器子產品的定義覆寫純Python實作的接口,以及重寫那些事先不知道的定義)。 

    當以這種方式重新釋出名稱時,以下關于公共和内部接口的準則仍然适用。

Module level dunder names 子產品級的“呆”名

__all__

 , 

__author__

 , 

__version__

 等這樣的子產品級“呆名“(也就是名字裡有兩個字首下劃線和兩個字尾下劃線),應該放在文檔字元串的後面,以及除from 

__future__

 之外的import表達式前面。Python要求将來在子產品中的導入,必須出現在除文檔字元串之外的其他代碼之前。 

比如:

"""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 字元串引号

在Python中,單引号和雙引号字元串是相同的。PEP不會為這個給出建議。選擇一條規則并堅持使用下去。當一個字元串中包含單引号或者雙引号字元的時候,使用和最外層不同的符号來避免使用反斜杠,進而提高可讀性。 

對于三引号字元串,總是使用雙引号字元來與PEP 257中的文檔字元串約定保持一緻。

Whitespace in Expressions and Statements 表達式和語句中的空格

Pet Peeves 不能忍受的事情

在下列情況下,避免使用無關的空格:

  • 緊跟在小括号,中括号或者大括号後。
Yes: spam(ham[1], {eggs: 2})
No:  spam( ham[ 1 ], { eggs: 2 } )
           
  • 緊貼在逗号、分号或者冒号之前。
Yes: if x == 4: print x, y; x, y = y, x
No:  if x == 4 : print x , y ; x , y = y , x
           
  • 然而,冒号在切片中就像二進制運算符,在兩邊應該有相同數量的空格(把它當做優先級最低的操作符)。在擴充的切片操作中,所有的冒号必須有相同的間距。例外情況:當一個切片參數被省略時,空格就被省略了。 

    推薦:

ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
           
  • 不推薦:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
           
  • 緊貼在函數參數的左括号之前。
Yes: spam(1)
No:  spam (1)
           
  • 緊貼索引或者切片的左括号之前。
Yes: dct['key'] = lst[index]
No:  dct ['key'] = lst [index]
           
  • 為了和另一個指派語句對齊,在指派運算符附件加多個空格。 
  • 推薦:
x = 1
y = 2
long_variable = 3
           

不推薦:

x             = 1
y             = 2
long_variable = 3
           

Other Recommendations 其他建議

  • 避免在尾部添加空格。因為尾部的空格通常都看不見,會産生混亂:比如,一個反斜杠後面跟一個空格的換行符,不算續行标記。有些編輯器不會保留尾空格,并且很多項目(像CPython)在pre-commit的挂鈎調用中會過濾掉尾空格。
  • 總是在二進制運算符兩邊加一個空格:指派(=),增量指派(+=,-=),比較(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),布爾(and, or, not)。
  • 如果使用具有不同優先級的運算符,請考慮在具有最低優先級的運算符周圍添加空格。有時需要通過自己來判斷;但是,不要使用一個以上的空格,并且在二進制運算符的兩邊使用相同數量的空格。 

    推薦:

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)
           

不推薦:

def complex(real, imag = 0.0):
    return magic(r = real, i = imag)
           
  • 功能型注釋應該使用冒号的一般性規則,并且在使用->的時候要在兩邊加空格。(參考下面的功能注釋得到能夠多資訊) 

    推薦:

def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
           

不推薦:

def munge(input:AnyStr): ...
def munge()->PosInt: ...
           
  • 當給有類型備注的參數指派的時候,在=兩邊添加空格(僅針對那種有類型備注和預設值的參數)。 

    推薦:

def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
           

不推薦:

def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
           
  • 複合語句(同一行中的多個語句)通常是不允許的。 

    推薦:

if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()
           

最好别這樣:

if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
           
  • 雖然有時候将小的代碼塊和 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()
           

Comments 注釋

與代碼相沖突的注釋比沒有注釋還糟,當代碼更改時,優先更新對應的注釋! 

注釋應該是完整的句子。如果一個注釋是一個短語或句子,它的第一個單詞應該大寫,除非它是以小寫字母開頭的辨別符(永遠不要改變辨別符的大小寫!)。 

如果注釋很短,結尾的句号可以省略。塊注釋一般由完整句子的一個或多個段落組成,并且每句話結束有個句号。 

在句尾結束的時候應該使用兩個空格。 

當用英文書寫時,遵循Strunk and White (譯注:《Strunk and White, The Elements of Style》)的書寫風格。 

在非英語國家的Python程式員,請使用英文寫注釋,除非你120%的确信你的代碼不會被使用其他語言的人閱讀。

Block Comments 塊注釋

塊注釋通常适用于跟随它們的某些(或全部)代碼,并縮進到與代碼相同的級别。塊注釋的每一行開頭使用一個#和一個空格(除非塊注釋内部縮進文本)。 

塊注釋内部的段落通過隻有一個#的空行分隔。

Inline Comments 行内注釋

有節制地使用行内注釋。 

行内注釋是與代碼語句同行的注釋。行内注釋和代碼至少要有兩個空格分隔。注釋由#和一個空格開始。 

事實上,如果狀态明顯的話,行内注釋是不必要的,反而會分散注意力。比如說下面這樣就不需要:

x = x + 1                 # Increment x
           
  • 但有時,這樣做很有用:
x = x + 1                 # Compensate for border
           

Documentation Strings 文檔字元串

編寫好的文檔說明(也叫“docstrings”)的約定在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 駝峰命名法 —— 這麼命名是因為字母看上去有起伏的外觀5)。有時候也被稱為StudlyCaps。 

    注意:當在首字母大寫的風格中用到縮寫時,所有縮寫的字母用大寫,是以,HTTPServerError 比 HttpServerError 好。

  • mixedCase(不同于首字母大寫,第一個單詞的首字母小寫)
  • Capitalized_Words_With_Underscores(巨醜無比!)

也有用唯一的短字首把相關命名組織在一起的方法。這在Python中不常用,但還是提一下。比如,os.stat()函數中包含類似以st_mode,st_size,st_mtime這種傳統命名方式命名的變量。(這麼做是為了與 POSIX 系統的調用一緻,以幫助程式員熟悉它。) 

X11庫的所有公共函數都加了字首X。在Python裡面沒必要這麼做,因為屬性和方法在調用的時候都會用類名做字首,函數名用子產品名做字首。 

另外,下面這種用字首或結尾下劃線的特殊格式是被認可的(通常和一些約定相結合):

  • _single_leading_underscore:(單下劃線開頭)弱“内部使用”訓示器。比如 from M import * 是不會導入以下劃線開始的對象的。
  • single_trailing_underscore_:(單下劃線結尾)這是避免和Python内部關鍵詞沖突的一種約定,比如:Tkinter.Toplevel(master, class_=’ClassName’)
  • __double_leading_underscore

    :(雙下劃線開頭)當這樣命名一個類的屬性時,調用它的時候名字會做矯正(在類FooBar中,

    __boo

    變成了

    _FooBar__boo

    ;見下文)。
  • __double_leading_and_trailing_underscore__

    :(雙下劃線開頭,雙下劃線結尾)“magic”對象或者存在于使用者控制的命名空間内的屬性,例如:

    __init__

    ,

    __import__

    或者

    __file__

    。除了作為文檔之外,永遠不要命這樣的名。

Prescriptive: Naming Conventions 約定俗成:命名約定

Names to Avoid 應避免的名字

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

在有些字型裡,這些字元無法和數字0和1區分,如果想用‘l’,用‘L’代替。

Package and Module Names 包名和子產品名

子產品應該用簡短全小寫的名字,如果為了提升可讀性,下劃線也是可以用的。Python包名也應該使用簡短全小寫的名字,但不建議用下劃線。 

當使用C或者C++編寫了一個依賴于提供進階(更面向對象)接口的Python子產品的擴充子產品,這個C/C++子產品需要一個下劃線字首(例如:_socket)

Class Names 類名

類名一般使用首字母大寫的約定。 

在接口被文檔化并且主要被用于調用的情況下,可以使用函數的命名風格代替。 

注意,對于内置的變量命名有一個單獨的約定:大部分内置變量是單個單詞(或者兩個單詞連接配接在一起),首字母大寫的命名法隻用于異常名或者内部的常量。

Exception Names 異常名

因為異常一般都是類,所有類的命名方法在這裡也适用。然而,你需要在異常名後面加上“Error”字尾(如果異常确實是一個錯誤)。

Global Variable Names 全局變量名

(我們希望這一類變量隻在子產品内部使用。)約定和函數命名規則一樣。 

通過 from M import * 導入的子產品應該使用all機制去防止内部的接口對外暴露,或者使用在全局變量前加下劃線的方式(表明這些全局變量是子產品内非公有)。

Function Names 函數名

函數名應該小寫,如果想提高可讀性可以用下劃線分隔。 

大小寫混合僅在為了相容原來主要以大小寫混合風格的情況下使用(比如 threading.py),保持向後相容性。

Function and method arguments 函數和方法參數

始終要将 self 作為執行個體方法的的第一個參數。 

始終要将 cls 作為類靜态方法的第一個參數。 

如果函數的參數名和已有的關鍵詞沖突,在最後加單一下劃線比縮寫或随意拼寫更好。是以 class_ 比 clss 更好。(也許最好用同義詞來避免這種沖突)

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,哪些隻能在基類中使用。 

貫徹這樣的思想,一下是一些讓代碼Pythonic的準則:

  • 公共屬性不應該有字首下劃線。
  • 如果公共屬性名和關鍵字沖突,在屬性名之後增加一個下劃線。這比縮寫和随意拼寫好很多。(然而,盡管有這樣的規則,在作為參數或者變量時,‘cls’是表示‘類’最好的選擇,特别是作為類方法的第一個參數。) 

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

  • 對于單一的共有屬性資料,最好直接對外暴露它的變量名,而不是通過負責的 存取器(accessor)/突變(mutator) 方法。請記住,如果你發現一個簡單的屬性需要成長為一個功能行為,那麼Python為這種将來會出現的擴充提供了一個簡單的途徑。在這種情況下,使用屬性去隐藏屬性資料通路背後的邏輯。 

    注意1:屬性隻在new-style類中起作用。 

    注意2:盡管功能方法對于類似緩存的負面影響比較小,但還是要盡量避免。 

    注意3:屬性标記會讓調用者認為開銷(相當的)小,避免用屬性做開銷大的計算。

  • 如果你的類打算用來繼承的話,并且這個類裡有不希望子類使用的屬性,就要考慮使用雙下劃線字首并且沒有字尾下劃線的命名方式。這會調用Python的命名轉換算法,将類的名字加入到屬性名裡。這樣做可以幫助避免在子類中不小心包含了相同的屬性名而産生的沖突。 

    注意1:隻有類名才會整合進屬性名,如果子類的屬性名和類名和父類都相同,那麼你還是會有命名沖突的問題。 

    注意2:命名轉換會在某些場景使用起來不太友善,例如調試,

    __getattr__()

    。然而命名轉換的算法有很好的文檔說明并且很好操作。 

    注意3:不是所有人都喜歡命名轉換。盡量避免意外的名字沖突和潛在的進階調用。

Public and internal interfaces 公共和内部的接口

任何向後相容保證隻适用于公共接口,是以,使用者清晰地區分公共接口和内部接口非常重要。 

文檔化的接口被認為是公開的,除非文檔明确聲明它們是臨時或内部接口,不受通常的向後相容性保證。所有未記錄的接口都應該是内部的。 

為了更好地支援内省(introspection),子產品應該使用

__all__

屬性顯式地在它們的公共API中聲明名稱。将

__all__

設定為空清單表示子產品沒有公共API。 

即使通過

__all__

設定過,内部接口(包,子產品,類,方法,屬性或其他名字)依然需要單個下劃線字首。 

如果一個命名空間(包,子產品,類)被認為是内部的,那麼包含它的接口也應該被認為是内部的。 

導入的名稱應該始終被視作是一個實作的細節。其他子產品必須不能間接通路這樣的名稱,除非它是包含它的子產品中有明确的文檔說明的API,例如 os.path 或者是一個包裡從子子產品公開函數接口的 

__init__

 子產品。

Programming Recommendations 程式設計建議

  • 代碼應該用不損害其他Python實作的方式去編寫(PyPy,Jython,IronPython,Cython,Psyco 等)。 

    比如,不要依賴于在CPython中高效的内置字元連接配接語句 a += b 或者 a = a + b。這種優化甚至在CPython中都是脆弱的(它隻适用于某些類型)并且沒有出現在不使用引用計數的實作中。在性能要求比較高的庫中,可以種 ”.join() 代替。這可以確定字元關聯在不同的實作中都可以以線性時間發生。

  • 和像None這樣的單例對象進行比較的時候應該始終用 is 或者 is not,永遠不要用等号運算符。 

    另外,如果你在寫 if x 的時候,請注意你是否表達的意思是 if x is not None。舉個例子,當測試一個預設值為None的變量或者參數是否被設定為其他值的時候。這個其他值應該是在上下文中能成為bool類型false的值。

  • 使用 is not 運算符,而不是 not … is 。雖然這兩種表達式在功能上完全相同,但前者更易于閱讀,是以優先考慮。 

    推薦:

if foo is not None:
           

不推薦:

if not foo is None:
           
  • 當使用富比較(rich comparisons,一種複雜的對象間比較的新機制,允許傳回值不為-1,0,1)實作排序操作的時候,最好實作全部的六個操作符(

    __eq__

    __ne__

    __lt__

    __gt__

    __ge__

    )而不是依靠其他的代碼去實作特定的比較。 

    為了最大程度減少這一過程的開銷, functools.total_ordering() 修飾符提供了用于生成缺少的比較方法的工具。 

    PEP 207 指出Python實作了反射機制。是以,解析器會将 y > x 轉變為 x < y,将 y >= x 轉變為 x <= y,也會轉換x == y 和 x != y的參數。sort() 和 min()方法確定使用<操作符,max()使用>操作符。然而,最好還是實作全部六個操作符,以免在其他地方出現沖突。

  • 始終使用def表達式,而不是通過指派語句将lambda表達式綁定到一個變量上。 

    推薦:

def f(x): return 2*x
           

不推薦:

f = lambda x: 2*x
           

第一個形式意味着生成的函數對象的名稱是“f”而不是泛型“< lambda >”。這在回溯和字元串顯示的時候更有用。指派語句的使用消除了lambda表達式優于顯式def表達式的唯一優勢(即lambda表達式可以内嵌到更大的表達式中)。

  • 從Exception繼承異常,而不是BaseException。直接繼承BaseException的異常适用于幾乎不用來捕捉的異常。 

    設計異常的等級,要基于撲捉異常代碼的需要,而不是異常抛出的位置。以程式設計的方式去回答“出了什麼問題?”,而不是隻是确認“出現了問題”(内置異常結構的例子參考 PEP 3151 ) 

    類的命名規範适用于這裡,但是你需要添加一個“Error”的字尾到你的異常類,如果異常是一個Error的話。非本地流控制或者其他形式的信号的非錯誤異常不需要特殊的字尾。

  • 适當地使用異常連結。在Python 3裡,為了不丢失原始的根源,可以顯式指定“raise X from Y”作為替代。 

    當故意替換一個内部異常時(Python 2 使用“raise X”, Python 3.3 之後 使用 “raise X from None”),確定相關的細節轉移到新的異常中(比如把AttributeError轉為KeyError的時候保留屬性名,或者将原始異常資訊的文本内容内嵌到新的異常中)。

  • 在Python 2中抛出異常時,使用 rasie ValueError(‘message’) 而不是用老的形式 raise ValueError, ‘message’。 

    第二種形式在Python3 的文法中不合法 

    使用小括号,意味着當異常裡的參數非常長,或者包含字元串格式化的時候,不需要使用換行符。

  • 當捕獲到異常時,如果可以的話寫上具體的異常名,而不是隻用一個except: 塊。 

    比如說:

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None
           

如果隻有一個except: 塊将會捕獲到SystemExit和KeyboardInterrupt異常,這樣會很難通過Control-C中斷程式,而且會掩蓋掉其他問題。如果你想捕獲所有訓示程式出錯的異常,使用 except Exception: (隻有except等價于 except BaseException:)。 

兩種情況不應該隻使用‘excpet’塊:

  1. 如果異常處理的代碼會列印或者記錄log;至少讓使用者知道發生了一個錯誤。
  2. 如果代碼需要做清理工作,使用 raise..try…finally 能很好處理這種情況并且能讓異常繼續上浮。
    • 當給捕捉的異常綁定一個名字時,推薦使用在Python 2.6中加入的顯式命名綁定文法:
try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))
           

為了避免和原來基于逗号分隔的文法出現歧義,Python3隻支援這一種文法。

  • 當捕捉作業系統的錯誤時,推薦使用Python 3.3 中errno内定數值指定的異常等級。
  • 另外,對于所有的 try/except 語句塊,在try語句中隻填充必要的代碼,這樣能避免掩蓋掉bug。 

    推薦:

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)
           
  • 第二個例子沒有提供任何資訊去指明

    __enter__

    __exit__

    方法在事務之後做出了關閉連接配接之外的其他事情。這種情況下,明确指明非常重要。
  • 傳回的語句保持一緻。函數中的傳回語句都應該傳回一個表達式,或者都不傳回。如果一個傳回語句需要傳回一個表達式,那麼在沒有值可以傳回的情況下,需要用 return None 顯式指明,并且在函數的最後顯式指定一條傳回語句(如果能跑到那的話)。 

    推薦:

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。如果需要相容Python2.0之前的版本可以不用考慮這個規則。

  • 使用 ”.startswith() 和 ”.endswith() 代替通過字元串切割的方法去檢查字首和字尾。 

    startswith()和endswith()更幹淨,出錯幾率更小。比如:

推薦: if foo.startswith('bar'):
糟糕: if foo[:3] == 'bar':
           
  • 對象類型的比較應該用isinstance()而不是直接比較type。
正确: if isinstance(obj, int):

糟糕: if type(obj) is type(1):
           

當檢查一個對象是否為string類型時,記住,它也有可能是unicode string!在Python2中,str和unicode都有相同的基類:basestring,是以你可以這樣:

if isinstance(obj, basestring):
           

注意,在Python3中,unicode和basestring都不存在了(隻有str)并且bytes類型的對象不再是string類型的一種(它是整數序列)

  • 對于序列來說(strings,lists,tuples),可以使用空序列為false的情況。
正确: if not seq:
      if seq:

糟糕: if len(seq):
      if not len(seq):
           
  • 書寫字元串時不要依賴單詞結尾的空格,這樣的空格在視覺上難以區分,有些編輯器會自動去掉他們(比如 reindent.py (譯注:re indent 重新縮進))
  • 不要用 == 去和True或者False比較:
正确: if greeting:
糟糕: if greeting == True:
更糟: if greeting is True:
           

Function Annotations 功能注釋

随着PEP 484的引入,功能型注釋的風格規範有些變化。

  • 為了向前相容,在Python3代碼中的功能注釋應該使用 PEP 484的文法規則。(在前面的章節中對注釋有格式化的建議。)
  • 不再鼓勵使用之前在PEP中推薦的實驗性樣式。
  • 然而,在stdlib庫之外,在PEP 484中的實驗性規則是被鼓勵的。比如用PEP 484的樣式标記大型的第三方庫或者應用程式,回顧添加這些注釋是否簡單,并觀察是否增加了代碼的可讀性。
  • Python的标準庫代碼應該保守使用這種注釋,但新的代碼或者大型的重構可以使用這種注釋。
  • 如果代碼希望對功能注釋有不同的用途,建議在檔案的頂部增加一個這種形式的注釋:
# type: ignore
           

這會告訴檢查器忽略所有的注釋。(在 PEP 484中可以找到從類型檢查器禁用投訴的更細粒度的方法。)

  • 像linters一樣,類型檢測器是可選的可獨立的工具。預設情況下,Python解釋器不應該因為類型檢查而發出任何消息,也不應該基于注釋改變它們的行為。
  • 不想使用類型檢測的使用者可以忽略他們。然而,第三方庫的使用者可能希望在這些庫上運作類型檢測。為此, PEP 484 建議使用存根檔案類型:.pyi檔案,這種檔案類型相比于.py檔案會被類型檢測器讀取。存根檔案可以和庫一起,或者通過typeshed repo6獨立釋出(通過庫作者的許可)
  • 對于需要向後相容的代碼,可以以注釋的形式添加功能型注釋。參見PEP 484的相關部分7。

參考

  1. PEP 7, Style Guide for C Code, van Rossum ↩
  2. Barry’s GNU Mailman style guide http://barry.warsaw.us/software/STYLEGUIDE.txt ↩
  3. 挂行縮進是一種類型設定樣式,其中除第一行之外,段落中的所有行都縮進。在Python中,這個術語是用來描述一種風格:在被括号括起來的語句中,左括号是這一行最後一個非空格字元,随後括号内的内容每一行進行縮進,直到遇到右括号。 ↩
  4. Donald Knuth’s The TeXBook, pages 195 and 196 ↩
  5. http://www.wikipedia.com/wiki/CamelCase ↩
  6. Typeshed repo https://github.com/python/typeshed ↩
  7. Suggested syntax for Python 2.7 and straddling code https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code ↩

原文連結:https://blog.csdn.net/ratsniper/article/details/78954852