PEP: | 8 |
---|---|
Title: | Style Guide for Python Code |
Version: | c451868df657 |
Last-Modified: | 2016-06-08 10:43:53 -0400 (Wed, 08 Jun 2016) |
Author: | Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com> |
Status: | Active |
Type: | Process |
Content-Type: | text/x-rst |
Created: | 05-Jul-2001 |
Post-History: | 05-Jul-2001, 01-Aug-2013 |
- 介紹
- 愚蠢的使用一致性是無知的怪物(A Foolish Consistency is the Hobgoblin of Little Minds)
- 代碼布局
- 縮進(jìn)
- 制表符還是空格
- 每行最大字符數(shù)
- 在二元運(yùn)算符之前是否換行
- 空行
- 源文件編碼
- 導(dǎo)入
- 模塊級的雙下劃線(dunder)名稱
- 字符串引號
- 表達(dá)式和語句中的空格
- 不可容忍
- 其他建議
- 注釋
- 塊注釋
- 行內(nèi)注釋
- 文檔字符串
- 命名規(guī)范
- 至高準(zhǔn)則
- 描述:命名風(fēng)格
- 約定俗成命名規(guī)定
- 應(yīng)避免的名稱
- 包名和模塊名
- 類名
- 異常名
- 全局變量名
- 函數(shù)名
- 函數(shù)和方法參數(shù)
- 方法名和實(shí)例變量
- 常量
- 繼承的設(shè)計(jì)
- 公共和內(nèi)部的接口
- 編程建議
- 功能注釋
- 參考
Introduction 介紹
本文檔提供了Python代碼的編碼約定,包括主Python發(fā)行版中的標(biāo)準(zhǔn)庫。請參閱Python的C實(shí)現(xiàn)中描述C代碼樣式指南的配套信息PEP [1]。
本文和PEP 257(Docstring約定)改編自Guido的原始Python風(fēng)格指南文章,并附有Barry的風(fēng)格指南[2]。
隨著時(shí)間的推移,隨著語言本身的變化,過去的約定被淘汰,這種風(fēng)格指南隨著時(shí)間的推移而不斷發(fā)展。
許多項(xiàng)目都有自己的編碼風(fēng)格指南。如果發(fā)生任何沖突,此類項(xiàng)目特定指南優(yōu)先于該項(xiàng)目。
愚蠢的使用一致性是無知的怪物(A Foolish Consistency is the Hobgoblin of Little Minds)
Guido的一個(gè)重要觀點(diǎn),代碼的讀取頻率遠(yuǎn)高于編寫代碼。此處提供的準(zhǔn)則旨在提高代碼的可讀性,并使其在各種Python代碼中保持一致。正如PEP 20所說,“可讀性很重要”。
風(fēng)格指南是關(guān)于一致性的。與此風(fēng)格指南的一致性非常重要。項(xiàng)目內(nèi)的一致性更為重要。一個(gè)模塊或功能內(nèi)的一致性是最重要的。
但是,知道何時(shí)不一致。有時(shí)風(fēng)格指南建議不適用。如有疑問,請使用您的最佳判斷。查看其他示例并確定最佳效果。并且不要猶豫,不要問!
特別是:不要為了遵守這個(gè)PEP而破壞向后兼容性!
忽略特定指南的其他一些好理由:
- 在應(yīng)用指南時(shí),即使是習(xí)慣于閱讀此PEP之后的代碼的人,也會(huì)使代碼的可讀性降低。
- 為了與周圍的代碼保持一致(也許是出于歷史原因) - 雖然這也是一個(gè)清理別人的混亂的機(jī)會(huì)(真正的XP風(fēng)格)。
- 因?yàn)橛袉栴}的代碼早于指南的引入,所以沒有其他理由可以修改該代碼。
- 當(dāng)代碼需要與不支持樣式指南推薦的功能的舊版Python兼容時(shí)。
代碼布局
縮進(jìn)
每個(gè)縮進(jìn)級別使用4個(gè)空格。
Python可以使用包含在小括號,中括號和大括號內(nèi)的方式使每行內(nèi)容連續(xù),或使用懸掛縮進(jìn),垂直對齊包裝元素。使用懸掛縮進(jìn)時(shí),應(yīng)考慮以下因素; 第一行應(yīng)該沒有參數(shù),應(yīng)該使用進(jìn)一步的縮進(jìn)來明確區(qū)分自己作為延續(xù)線。
懸掛縮進(jìn)是一種類型設(shè)置樣式,其中段落中的所有行都縮進(jìn),但第一行除外。在Python的上下文中,該術(shù)語用于描述一種樣式,其中帶括號的語句的左括號是該行的最后一個(gè)非空白字符,后續(xù)行縮進(jìn)到右括號。
Yes:
# 與左括號對齊
foo = long_function_name(var_one,var_two,
var_three,var_four)
# 用更多的縮進(jìn)來區(qū)分其他行
def long_function_name(
var_one,var_two,var_three,
var_four):
print(var_one)
# 懸掛縮進(jìn)應(yīng)該增加一級
foo = long_function_name(
var_one, var_two,
var_three, var_four)
No:
# 有變量在第一行時(shí)禁止使用懸掛縮進(jìn)
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 當(dāng)縮進(jìn)沒有與其他行區(qū)分時(shí),要增加縮進(jìn)
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
四空格的規(guī)則對于續(xù)行是可選的。
Option:
# 懸掛縮進(jìn)*可以*縮進(jìn)量可以不是4個(gè)空格
foo = long_function_name(
var_one, var_two,
var_three, var_four)
當(dāng)if語句的條件部分足夠長以要求它跨多行寫入時(shí),值得注意的是兩個(gè)字符關(guān)鍵字(即if)加上一個(gè)空格一個(gè)左括號的組合,創(chuàng)建了一個(gè)自然的多行條件的后續(xù)行的4空格縮進(jìn)。這可能與嵌套在if語句中的縮進(jìn)代碼集產(chǎn)生視覺沖突,該代碼集自然也會(huì)縮進(jìn)到4個(gè)空格。該P(yáng)EP沒有明確地說明如何(或是否)進(jìn)一步在視覺上將這些條件線與if語句內(nèi)的嵌套套件區(qū)分開來。在這種情況下可接受的選擇包括但不限于:
# 沒有額外的縮進(jìn)
if (this_is_one_thing and
that_is_another_thing):
do_something()
#添加注釋,這將在編輯器中提供一些區(qū)別
#支持語法突出顯示。
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()
#在條件連續(xù)行上添加一些額外的縮進(jìn)。
if (this_is_one_thing
and that_is_another_thing):
do_something()
(另請參閱下面關(guān)于是否在二元運(yùn)算符之前或之后中斷的討論。)
多行結(jié)構(gòu)上的小括號/中括號/大括號可以在列表最后一行的第一個(gè)非空白字符下排列,如下所示:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
或者它可以排在啟動(dòng)多行結(jié)構(gòu)的行的第一個(gè)字符下面,如:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
制表符還是空格
空格是首選的縮進(jìn)方法。
制表符應(yīng)僅用于與已使用制表符縮進(jìn)的代碼保持一致。
Python 3不允許混合使用制表符和空格來縮進(jìn)。
使用制表符和空格的混合縮進(jìn)的Python 2代碼應(yīng)該轉(zhuǎn)換為僅使用空格。
當(dāng)使用-t選項(xiàng)調(diào)用Python 2命令行解釋器時(shí),它會(huì)發(fā)出有關(guān)非法混合制表符和空格的代碼的警告。使用-tt時(shí),這些警告會(huì)出錯(cuò)。強(qiáng)烈推薦這些選項(xiàng)!
每行最大字符數(shù)
將所有行限制為最多79個(gè)字符。
對于具有較少結(jié)構(gòu)限制(文檔字符串或注釋)的長文本塊,行長度應(yīng)限制為72個(gè)字符。
限制所需的編輯器窗口寬度使得可以并排打開多個(gè)文件,并且在使用在相鄰列中顯示兩個(gè)版本的代碼審查工具時(shí)可以正常工作。
大多數(shù)工具中的默認(rèn)包裝會(huì)破壞代碼的可視化結(jié)構(gòu),使其更難理解。選擇限制是為了避免在窗口寬度設(shè)置為80的情況下包裝在編輯器中,即使工具在包裝線條時(shí)在最終列中放置標(biāo)記字形。某些基于Web的工具可能根本不提供動(dòng)態(tài)換行。
有些團(tuán)隊(duì)強(qiáng)烈傾向于更長的線路長度。對于專門或主要由可以就此問題達(dá)成一致的團(tuán)隊(duì)維護(hù)的代碼,可以將標(biāo)稱行長度從80個(gè)字符增加到100個(gè)字符(有效地將最大長度增加到99個(gè)字符),前提是評論和文檔字符串仍然包裝72個(gè)字符。
Python標(biāo)準(zhǔn)庫是保守的,需要將行限制為79個(gè)字符(文檔字符串/注釋限制為72個(gè))。
包裝長行的首選方法是在小括號,中括號和大括號內(nèi)使用Python隱含的行繼續(xù)。通過在括號中包裝表達(dá)式,可以在多行上分割長行。這些應(yīng)該優(yōu)先于反斜杠的行繼續(xù)。
反斜杠的行繼續(xù)在以下情形適用。例如,long,多重with語句不能使用隱式延續(xù),因此可以接受反斜杠:
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())
(請參閱前面關(guān)于多行if語句的討論,以獲得關(guān)于這種多重with語句縮進(jìn)的進(jìn)一步想法。)
另一種類似情況是使用assert語句。
確保在續(xù)行進(jìn)行適當(dāng)?shù)目s進(jìn)。
在二元運(yùn)算符之前是否換行
幾十年來,推薦的風(fēng)格是在二元運(yùn)算符之后打破。但這會(huì)以兩種方式損害可讀性:操作員傾向于分散在屏幕上的不同列上,并且每個(gè)操作符都會(huì)從其操作數(shù)移到前一行。在這里,眼睛必須做額外的工作來分辨哪些對象使用加法以及哪些對象使用減法:
# No: 對象遠(yuǎn)離操作符
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
為了解決這個(gè)可讀性問題,數(shù)學(xué)家和他們的出版商遵循相反的慣例。Donald Knuth在他的《計(jì)算機(jī)和排版》系列中解釋了傳統(tǒng)規(guī)則:“雖然段落中的公式總是在二元操作和關(guān)系之后中斷,但顯示的公式總是在二元操作之前中斷” [3]。
遵循數(shù)學(xué)傳統(tǒng)通常會(huì)產(chǎn)生更易讀的代碼:
# Yes:運(yùn)算符和操作數(shù)很容易進(jìn)行匹配
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
在Python代碼中,只要約定在本地一致,就允許在二元運(yùn)算符之前或之后中斷。對于新代碼,建議使用Knuth的樣式。
空行
使用兩個(gè)空行環(huán)繞頂級函數(shù)和類定義。
類中的方法定義由單個(gè)空行包圍。
可以使用額外的空白行(謹(jǐn)慎地)來分離相關(guān)功能組。在一堆相關(guān)的單行(例如,一組虛擬實(shí)現(xiàn))之間可以省略空行。
在函數(shù)中使用空行,謹(jǐn)慎地指示邏輯部分。
Python接受control-L(即^ L)換頁符作為空格; 許多工具將這些字符視為頁面分隔符,因此您可以使用它們來分隔文件相關(guān)部分的頁面。請注意,某些編輯器和基于Web的代碼查看器可能無法將control-L識別為換頁符,并且會(huì)在其位置顯示另一個(gè)字形。
源文件編碼
核心Python發(fā)行版中的代碼應(yīng)始終使用UTF-8(或Python 2中的ASCII)。
使用ASCII(在Python 2中)或UTF-8(在Python 3中)的文件不應(yīng)具有編碼聲明。
在標(biāo)準(zhǔn)庫中,非默認(rèn)編碼應(yīng)僅用于測試目的,或者當(dāng)注釋或文檔字符串需要提及包含非ASCII字符的作者名稱時(shí); 否則,使用\x,\u,\U或\N轉(zhuǎn)義是在字符串文字中包含非ASCII數(shù)據(jù)的首選方法。
對于Python 3.0及更高版本,標(biāo)準(zhǔn)庫規(guī)定了以下策略(參見PEP 3131):Python標(biāo)準(zhǔn)庫中的所有標(biāo)識符必須使用僅ASCII標(biāo)識符,并且應(yīng)盡可能使用英語單詞(在許多情況下,縮寫和技術(shù))使用的術(shù)語不是英語)。此外,字符串文字和注釋也必須是ASCII格式。唯一的例外是
- 測試非ASCII功能的測試用例;
- 作者姓名。名字不是基于拉丁字母的作者必須提供他們名字的拉丁音譯。
鼓勵(lì)全球受眾的開源項(xiàng)目采用類似的政策。
導(dǎo)入
- 導(dǎo)入通常在分開的行,例如:
# Yes: 分開的行
import os
import sys
# No: 在一行
import sys, os
但是可以這樣:
from subprocess import Popen, PIPE
-
導(dǎo)入總是位于文件的頂部,在模塊注釋和文檔字符串之后,在模塊的全局變量與常量之前。
導(dǎo)入應(yīng)該按照以下順序分組:- 標(biāo)準(zhǔn)庫導(dǎo)入
- 相關(guān)第三方庫導(dǎo)入
- 本地應(yīng)用/庫特定導(dǎo)入
你應(yīng)該在每一組導(dǎo)入之間加入空行。
建議使用絕對導(dǎo)入,因?yàn)槿绻麑?dǎo)入系統(tǒng)配置不正確(例如,當(dāng)包中的目錄最終出現(xiàn)在sys.path上時(shí)),它們通常更具可讀性并且往往表現(xiàn)得更好(或至少提供更好的錯(cuò)誤消息):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
然而,顯示的指定相對導(dǎo)入路徑是使用絕對路徑的一個(gè)可接受的替代方案,特別是在處理使用絕對路徑導(dǎo)入不必要冗長的復(fù)雜包布局時(shí):
from . import sibling
from .sibling import example
標(biāo)準(zhǔn)庫代碼應(yīng)避免復(fù)雜的包布局,并始終使用絕對導(dǎo)入。
永遠(yuǎn)不應(yīng)該使用隱式相對導(dǎo)入,并且已經(jīng)在Python 3中刪除了。
- 從包含類的模塊導(dǎo)入類時(shí),通常可以拼寫:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果此拼寫導(dǎo)致本地名稱沖突,則拼寫它們
import myclass
import foo.bar.yourclass
并使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”。
- 應(yīng)該避免使用通配符導(dǎo)入(來自<module> import *),因?yàn)樗鼈儾磺宄臻g中存在哪些名稱,使讀者和許多自動(dòng)化工具混淆。通配符導(dǎo)入有一個(gè)可防御的用例,即將內(nèi)部接口重新發(fā)布為公共API的一部分(例如,使用可選加速器模塊中的定義覆蓋接口的純Python實(shí)現(xiàn),以及確切的定義將是被覆蓋的事先不知道)。
以這種方式重新發(fā)布名稱時(shí),以下有關(guān)公共和內(nèi)部接口的指南仍然適用。
模塊級的雙下劃線(dunder)名稱
模塊級“dunder名“(以雙下劃線作為開頭和結(jié)尾的名稱),如all,author,version等,除了future以為,應(yīng)被放置在模塊文檔字符串之后,以及除from future 之外的import表達(dá)式之前。Python要求將來在模塊中的導(dǎo)入,必須出現(xiàn)在除文檔字符串之外的其他代碼之前。
比如:
"""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
字符串引號
在Python中,單引號字符串和雙引號字符串是相同的。該P(yáng)EP不會(huì)對此提出建議。選擇規(guī)則并堅(jiān)持下去。但是,當(dāng)字符串包含單引號或雙引號字符時(shí),請使用另一個(gè)字符串以避免字符串中出現(xiàn)反斜杠。它提高了可讀性。
對于三引號字符串,始終使用雙引號字符與PEP 257中的docstring約定一致。
表達(dá)式和語句中的空格
不可容忍
- 在以下情況下避免無關(guān)的空格:
緊靠括號,括號或括號內(nèi)。
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
- 但是,在切片中,冒號的行為類似于二元運(yùn)算符,并且兩側(cè)的數(shù)量應(yīng)該相等(將其視為具有最低優(yōu)先級的運(yùn)算符)。在擴(kuò)展切片中,兩個(gè)冒號必須具有相同的間距。例外:省略slice參數(shù)時(shí),省略空格。
Yes:
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]
No:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
- 緊接在啟動(dòng)函數(shù)調(diào)用的參數(shù)列表的左括號之前:
Yes: spam(1)
No: spam (1)
- 緊接在開始索引或切片的左括號之前:
Yes: dct['key'] = lst[index]
No: dct ['key'] = lst [index]
- 賦值(或其他)運(yùn)算符周圍有多個(gè)空格,以使其與另一個(gè)運(yùn)算符對齊。
Yes:
x = 1
y = 2
long_variable = 3
No:
x = 1
y = 2
long_variable = 3
其他建議
避免在任何地方尾隨空格。因?yàn)樗ǔJ遣豢梢姷模运赡軙?huì)令人困惑:例如,反斜杠后跟空格和換行符不算作行繼續(xù)標(biāo)記。有些編輯器不保留它,許多項(xiàng)目(如CPython本身)都有預(yù)先提交的拒絕它的鉤子。
始終圍繞這些二元運(yùn)算符,兩邊都有一個(gè)空格:賦值(
=
),擴(kuò)充賦值(+=
,-=
等),比較(==
,<
,>
,!=
,<>
,<=
,>=
,in
,not in
,is
,is not
),布爾(and
,or
,not
)。如果使用具有不同優(yōu)先級的運(yùn)算符,請考慮在具有最低優(yōu)先級的運(yùn)算符周圍添加空格。用你自己的判斷; 但是,永遠(yuǎn)不要使用多個(gè)空格,并且在二元運(yùn)算符的兩邊始終具有相同數(shù)量的空白。
Yes:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
No:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
- 當(dāng)用于指示關(guān)鍵字參數(shù)或默認(rèn)參數(shù)值時(shí),請勿在=符號周圍使用空格。
Yes:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
No:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
- 函數(shù)注釋應(yīng)該使用冒號的常規(guī)規(guī)則,并且如果存在,則始終在->箭頭周圍留出空格。(有關(guān)函數(shù)注釋的更多信息,請參閱 下面的函數(shù)注釋。)
Yes:
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
No:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
- 當(dāng)給有類型備注的參數(shù)賦值的時(shí)候,在
=
兩邊添加空格(僅針對那種有類型備注和默認(rèn)值的參數(shù))。
Yes:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
No:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
- 復(fù)合語句(同一行中的多個(gè)語句)通常是不允許的。
Yes:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
Rather not:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
- 雖然有時(shí)候?qū)⑿〉拇a塊和
if
/for
/while
放在同一行沒什么問題,多行語句塊的情況不要這樣用,同樣也要避免代碼行太長!
Rather not:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
Definitely not:
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()
注釋
與代碼相矛盾的注釋比沒有注釋更糟糕。始終優(yōu)先考慮在代碼更改時(shí)保持評論的最新狀態(tài)!
注釋應(yīng)該是完整的句子。如果注釋是短語或句子,則其第一個(gè)單詞應(yīng)該大寫,除非它是以小寫字母開頭的標(biāo)識符(永遠(yuǎn)不會(huì)改變標(biāo)識符的情況!)。
如果注釋很短,則可以省略最后的句點(diǎn)。塊注釋通常由完整句子構(gòu)成的一個(gè)或多個(gè)段落組成,每個(gè)句子應(yīng)以句點(diǎn)結(jié)束。
在句子結(jié)束期后你應(yīng)該使用兩個(gè)空格。
當(dāng)用英文書寫時(shí),遵循Strunk and White 的書寫風(fēng)格。
來自非英語國家的Python程序員:請用英語撰寫您的注釋,除非您120%確信不會(huì)說不懂您語言的人不會(huì)閱讀該代碼。。
塊注釋
塊注釋通常適用于跟隨它們的某些(或全部)代碼,并縮進(jìn)到與代碼相同的級別。塊注釋的每一行開頭使用一個(gè)#和一個(gè)空格(除非塊注釋內(nèi)部縮進(jìn)文本)。
塊注釋內(nèi)部的段落通過只有一個(gè)#的空行分隔。
行內(nèi)注釋
有節(jié)制地使用行內(nèi)注釋。
行內(nèi)注釋是與代碼語句同行的注釋。行內(nèi)注釋和代碼至少要有兩個(gè)空格分隔。注釋由#和一個(gè)空格開始。
事實(shí)上,如果狀態(tài)明顯的話,行內(nèi)注釋是不必要的,反而會(huì)分散注意力。比如說下面這樣就不需要:
x = x + 1 # Increment x
但有時(shí),這樣做很有用:
x = x + 1 # Compensate for border
文檔字符串
編寫好的文檔字符串(又名“docstrings”)的約定在PEP 257中是永恒的。
- 為所有公共模塊,函數(shù),類和方法編寫文檔字符串。對于非公共方法,文檔字符串不是必需的,但是您應(yīng)該有一個(gè)注釋來描述該方法的作用。此評論應(yīng)出現(xiàn)在def行之后。
- PEP 257描述了良好的文檔字符串約定。請注意,最重要的是,結(jié)束多行文檔字符串的"""應(yīng)該單獨(dú)在一行上,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 對于單行的文檔說明,尾部的"""應(yīng)該和文檔在同一行。
命名規(guī)范
Python庫的命名規(guī)范很亂,從來沒能做到完全一致。但是目前有一些推薦的命名標(biāo)準(zhǔn)。新的模塊和包(包括第三方框架)應(yīng)該用這套標(biāo)準(zhǔn),但當(dāng)一個(gè)已有庫采用了不同的風(fēng)格,推薦保持內(nèi)部一致性。
至高原則
那些暴露給用戶的API接口的命名,應(yīng)該遵循反映使用場景而不是實(shí)現(xiàn)的原則。
描述:命名風(fēng)格
有許多不同的命名風(fēng)格。這里能夠幫助大家識別正在使用什么樣的命名風(fēng)格,而不考慮他們?yōu)槭裁词褂谩?br> 以下是常見的命名方式:
- b (單個(gè)小寫字母)
- B (單個(gè)大寫字母)
- lowercase 小寫字母
- lower_case_with_underscores 使用下劃線分隔的小寫字母
- UPPERCASE 大寫字母
- UPPER_CASE_WITH_UNDERSCORES 使用下劃線分隔的大寫字母
- CapitalizedWords(或CapWords,或CamelCase -- 因其字母凹凸不平而得名[4])。這有時(shí)也被稱為StudlyCaps。
注意:當(dāng)在首字母大寫的風(fēng)格中用到縮寫時(shí),所有縮寫的字母用大寫,因此,HTTPServerError 比 HttpServerError 好。 - mixedCase (不同于首字母大寫,第一個(gè)單詞的首字母小寫)
- Capitalized_Words_With_Underscores (ugly! )
還有使用簡短唯一前綴將相關(guān)名稱組合在一起的風(fēng)格。這在Python中并不常用,但為了完整性而提到它。例如,os.stat()函數(shù)返回一個(gè)元組,其元素傳統(tǒng)上具有st_mode, st_size,st_mtime等名稱。(這樣做是為了強(qiáng)調(diào)與POSIX系統(tǒng)調(diào)用struct的字段的對應(yīng)關(guān)系,這有助于程序員熟悉它。)
X11庫的所有公共函數(shù)都加了前綴X。在Python里面沒必要這么做,因?yàn)閷傩院头椒ㄔ谡{(diào)用的時(shí)候都會(huì)用類名做前綴,函數(shù)名用模塊名做前綴。
另外,下面這種用前綴或結(jié)尾下劃線的特殊格式是被認(rèn)可的(通常和一些約定相結(jié)合):
single_leading_underscore
:弱“內(nèi)部使用”指標(biāo)。例如,from M import *
不會(huì)導(dǎo)入名稱以下劃線開頭的對象。-
single_trailing_underscore_
:(單下劃線結(jié)尾)這是避免和Python內(nèi)部關(guān)鍵詞沖突的一種約定,比如:Tkinter.Toplevel(master, class_=’ClassName’)
__double_leading_underscore
:在命名一個(gè)class屬性時(shí),調(diào)用name mangling(在類FooBar中,__ boo
變成_FooBar__boo
;見下文)。__double_leading_and_trailing_underscore__
:生成在用戶控制的命名空間中的“魔術(shù)”對象或?qū)傩浴@?code>__init __,__import__
或__file__
。不要發(fā)明這樣的名字; 僅按記錄使用它們。
約定
應(yīng)避免的名字
永遠(yuǎn)不要使用字母‘l’(小寫的L),‘O’(大寫的O),或者‘I’(大寫的I)作為單字符變量名。
在有些字體里,這些字符無法和數(shù)字0和1區(qū)分,如果想用‘l’,用‘L’代替。
包名和模塊名
模塊應(yīng)該有簡短的全小寫名稱。如果提高可讀性,可以在模塊名稱中使用下劃線。Python包也應(yīng)該有簡短的全小寫名稱,但不鼓勵(lì)使用下劃線。
當(dāng)用C或C ++編寫的擴(kuò)展模塊具有提供更高級別(例如更多面向?qū)ο螅┑慕涌诘腜ython模塊時(shí),C/C ++模塊具有前導(dǎo)下劃線(例如_socket
)。
類名
類名通常應(yīng)使用CapWords約定。
在接口被記錄并主要用作可調(diào)用的情況下,可以使用函數(shù)的命名約定。
請注意,內(nèi)置名稱有一個(gè)單獨(dú)的約定:大多數(shù)內(nèi)置名稱是單個(gè)單詞(或兩個(gè)單詞一起運(yùn)行),CapWords約定僅用于異常名稱和內(nèi)置常量。
異常名
因?yàn)楫惓?yīng)該是類,所以類命名約定適用于此處。但是,您應(yīng)該在異常名稱上使用后綴"Error"(如果異常實(shí)際上是錯(cuò)誤)。
全局變量名
(我們希望這些變量僅用于一個(gè)模塊內(nèi)。)約定與函數(shù)的約定大致相同。
設(shè)計(jì)為通過M import *使用的模塊應(yīng)該使用__all__
機(jī)制來防止輸出全局變量,或者使用舊的約定為這些全局變量添加下劃線(您可能希望這樣做以表明這些全局變量是“模塊非公開的” )。
函數(shù)名
函數(shù)名應(yīng)該小寫,如果想提高可讀性可以用下劃線分隔。
大小寫混合僅在為了兼容原來主要以大小寫混合風(fēng)格的情況下使用(比如 threading.py),保持向后兼容性。
函數(shù)和方法參數(shù)
始終要將 self 作為實(shí)例方法的的第一個(gè)參數(shù)。
始終要將 cls 作為類靜態(tài)方法的第一個(gè)參數(shù)。
如果函數(shù)的參數(shù)名和已有的關(guān)鍵詞沖突,在最后加單一下劃線比縮寫或隨意拼寫更好。因此 class_ 比 clss 更好。(也許最好用同義詞來避免這種沖突)
方法名和實(shí)例變量
使用函數(shù)命名規(guī)則:小寫,必要時(shí)用下劃線分隔,以提高可讀性。
僅對非公共方法和實(shí)例變量使用一個(gè)前導(dǎo)下劃線。
為避免與子類的名稱沖突,請使用兩個(gè)前導(dǎo)下劃線來調(diào)用Python的名稱修改規(guī)則。
Python使用類名來破壞這些名稱:如果類Foo
具有名為__a
的屬性,則Foo.__ a
無法訪問它。(堅(jiān)持不懈的用戶仍然可以通過調(diào)用Foo._Foo__a
獲得訪問權(quán)限。)通常,雙重前導(dǎo)下劃線應(yīng)該僅用于避免與設(shè)計(jì)為子類的類中的屬性發(fā)生名稱沖突。
注意:關(guān)于__names
的使用存在一些爭議(見下文)。
常量
常量通常在模塊級別定義,并以全部大寫字母書寫,下劃線分隔單詞。示例包括 MAX_OVERFLOW
和TOTAL
。
繼承的設(shè)計(jì)
始終決定一個(gè)類的方法和實(shí)例變量(統(tǒng)稱為“屬性”)是公共的還是非公共的。如有疑問,請選擇非公開; 將公共屬性設(shè)為非公開更容易公開。
公共屬性是指您希??望類的無關(guān)客戶端使用的屬性,您承諾避免向后不兼容的更改。非公開屬性是指不打算由第三方使用的屬性; 您不能保證非公共屬性不會(huì)更改甚至不會(huì)被刪除。
我們在這里不使用術(shù)語“私有”,因?yàn)樵赑ython中沒有屬性是真正私有的(沒有通常不必要的工作量)。
另一類屬性是屬于“子類API”的屬性(通常在其他語言中稱為“受保護(hù)”)。某些類旨在從中繼承,以擴(kuò)展或修改類的行為方面。在設(shè)計(jì)這樣的類時(shí),請注意明確決定哪些屬性是公共的,哪些是子類API的一部分,哪些屬性真正只能由基類使用。
考慮到這一點(diǎn),這是Pythonic指南:
公共屬性應(yīng)該沒有前導(dǎo)下劃線。
如果公共屬性名稱與保留關(guān)鍵字沖突,請?jiān)趯傩悦Q后附加單個(gè)尾隨下劃線。這比縮寫或損壞的拼寫更可取。(但是,盡管有這個(gè)規(guī)則,'cls'是任何已知為類的變量或參數(shù)的首選拼寫,尤其是類方法的第一個(gè)參數(shù)。)
注1:有關(guān)類方法,請參閱上面的參數(shù)名稱建議。
對于簡單的公共數(shù)據(jù)屬性,最好只公開屬性名稱,而不使用復(fù)雜的訪問器/ mutator方法。請記住,如果您發(fā)現(xiàn)簡單的數(shù)據(jù)屬性需要增加功能行為,Python提供了一條簡單的未來增強(qiáng)路徑。在這種情況下,使用屬性隱藏簡單數(shù)據(jù)屬性訪問語法背后的功能實(shí)現(xiàn)。
注1:屬性僅適用于新式類。
注2:嘗試保持功能行為副作用免費(fèi),盡管緩存等副作用通常很好。
注3:避免使用屬性進(jìn)行計(jì)算成本高昂的操作; 屬性表示法使調(diào)用者相信訪問(相對)便宜。
如果您的類要進(jìn)行子類化,并且您具有不希望使用子類的屬性,請考慮使用雙前導(dǎo)下劃線和沒有尾隨下劃線來命名它們。這將調(diào)用Python的名稱修改方法,其中類的名稱被修改為屬性名稱。如果子類無意中包含具有相同名稱的屬性,這有助于避免屬性名稱沖突。
注1:請注意,在修改的名稱中只使用簡單的類名,因此如果子類選擇相同的類名和屬性名,則仍然可以獲得名稱沖突。
注2:名稱修改可以使某些用途,例如調(diào)試和 __getattr__()
,不太方便。但是,名稱修改算法已有詳細(xì)記錄,并且易于手動(dòng)執(zhí)行。
注3:不是每個(gè)人都喜歡誤拼。盡量在避免意外姓名沖突與潛在的高級調(diào)用間尋求平衡。
公共和內(nèi)部的接口
任何向后兼容性保證僅適用于公共接口。因此,用戶能夠清楚地區(qū)分公共和內(nèi)部接口是很重要的。
記錄的接口被認(rèn)為是公共的,除非文檔明確聲明它們是臨時(shí)的或內(nèi)部接口免于通常的向后兼容性保證。應(yīng)假定所有未記錄的接口都是內(nèi)部接口。
為了更好地支持內(nèi)省,模塊應(yīng)使用__all__
屬性在其公共API中顯式聲明名稱。將__all__
設(shè)置 為空列表表示該模塊沒有公共API。
即使適當(dāng)?shù)卦O(shè)置__all__
,內(nèi)部接口(包,模塊,類,函數(shù),屬性或其他名稱)仍應(yīng)以單個(gè)前導(dǎo)下劃線為前綴。
如果任何包含名稱空間(包,模塊或類)的內(nèi)容被視為內(nèi)部接口,則該接口也被視為內(nèi)部接口。
應(yīng)始終將導(dǎo)入的名稱視為實(shí)現(xiàn)細(xì)節(jié)。其他模塊不能依賴于對這些導(dǎo)入名稱的間接訪問,除非它們是包含模塊的API的顯式記錄部分,例如os.path
或從子模塊公開功能的包的__init__
模塊。
編程建議
- 代碼應(yīng)該用不損害其他Python實(shí)現(xiàn)的方式去編寫(PyPy,Jython,IronPython,Cython,Psyco 等)。
比如,不要依賴于在CPython中高效的內(nèi)置字符連接語句a += b
或者a = a + b
。這種優(yōu)化甚至在CPython中都是脆弱的(它只適用于某些類型)并且沒有出現(xiàn)在不使用引用計(jì)數(shù)的實(shí)現(xiàn)中。在性能要求比較高的庫中,可以種''.join()
代替。這可以確保字符關(guān)聯(lián)在不同的實(shí)現(xiàn)中都可以以線性時(shí)間發(fā)生。 - 和像None這樣的單例對象進(jìn)行比較的時(shí)候應(yīng)該始終用 is 或者 is not,永遠(yuǎn)不要用等號運(yùn)算符。
另外,如果你在寫if x
的時(shí)候,請注意你是否表達(dá)的意思是if x is not None
。舉個(gè)例子,當(dāng)測試一個(gè)默認(rèn)值為None的變量或者參數(shù)是否被設(shè)置為其他值的時(shí)候。這個(gè)其他值應(yīng)該是在上下文中能成為bool類型false的值。 - 使用 is not 運(yùn)算符,而不是 not … is 。雖然這兩種表達(dá)式在功能上完全相同,但前者更易于閱讀,所以優(yōu)先考慮。
Yes:
if foo is not None:
No:
if not foo is None:
- 當(dāng)具富比較排序操作,最好是實(shí)現(xiàn)所有六個(gè)操作(
__eq__
,__ne__
,__lt__
,__le__
,__gt__
,__ge__
)而不是依靠其他代碼,只行使特定的比較。
為了最大限度地減少所涉及的工作量,functools.total_ordering()
裝飾器提供了一個(gè)生成缺失比較方法的工具。
PEP 207 指出Python實(shí)現(xiàn)了反射機(jī)制。因此,解析器會(huì)將 y > x
轉(zhuǎn)變?yōu)?x < y
,將 y >= x
轉(zhuǎn)變?yōu)?x <= y
,也會(huì)轉(zhuǎn)換 x == y
和 x != y
的參數(shù)。sort() 和 min()方法確保使用<操作符,max()使用>操作符。然而,最好還是實(shí)現(xiàn)全部六個(gè)操作符,以免在其他地方出現(xiàn)沖突。
- 始終使用def表達(dá)式,而不是通過賦值語句將lambda表達(dá)式綁定到一個(gè)變量上。
Yes:
def f(x): return 2*x
No:
f = lambda x: 2*x
第一種形式意味著生成的函數(shù)對象的名稱特別是'f'而不是泛型'<lambda>'。這對于一般的回溯和字符串表示更有用。使用賦值語句消除了lambda表達(dá)式可以在顯式def語句上提供的唯一好處(即它可以嵌入到更大的表達(dá)式中)
- 從Exception而不是BaseException派生異常。BaseException的直接繼承保留用于捕獲它們的異常幾乎總是錯(cuò)誤的事情。
基于可能需要捕獲異常的代碼的區(qū)別來設(shè)計(jì)異常層次結(jié)構(gòu) ,而不是引發(fā)異常的位置。旨在回答“出了什么問題?”的問題。以編程方式,而不是僅僅聲明“發(fā)生了一個(gè)問題”(請參閱PEP 3151,了解本課程的示例是為內(nèi)置異常層次結(jié)構(gòu)學(xué)習(xí)的)
類命名約定適用于此處,但如果異常是錯(cuò)誤,則應(yīng)將后綴“Error”添加到異常類中。用于非本地流控制或其他形式的信令的非錯(cuò)誤異常不需要特殊后綴。
類的命名規(guī)范適用于這里,但是你需要添加一個(gè)“Error”的后綴到你的異常類,如果異常是一個(gè)Error的話。非本地流控制或者其他形式的信號的非錯(cuò)誤異常不需要特殊的后綴。
適當(dāng)?shù)厥褂卯惓f溄印T赑ython 3里,為了不丟失原始的根源,可以顯式指定
raise X from Y
作為替代。
當(dāng)故意替換一個(gè)內(nèi)部異常時(shí)(Python 2 使用“raise X”, Python 3.3 之后 使用raise X from None
),確保相關(guān)的細(xì)節(jié)轉(zhuǎn)移到新的異常中(比如把AttributeError轉(zhuǎn)為KeyError的時(shí)候保留屬性名,或者將原始異常信息的文本內(nèi)容內(nèi)嵌到新的異常中)。在Python 2中拋出異常時(shí),使用
rasie ValueError('message')
而不是用老的形式raise ValueError, 'message'
。
第二種形式在Python3 的語法中不合法
使用小括號,意味著當(dāng)異常里的參數(shù)非常長,或者包含字符串格式化的時(shí)候,不需要使用換行符。當(dāng)捕獲到異常時(shí),如果可以的話寫上具體的異常名,而不是只用一個(gè)
except:
塊。
比如:
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
如果只有一個(gè)except
塊將會(huì)捕獲到SystemExit
和KeyboardInterrupt
異常,這樣會(huì)很難通過Control-C中斷程序,而且會(huì)掩蓋掉其他問題。如果你想捕獲所有指示程序出錯(cuò)的異常,使用 except Exception:
(只有except
等價(jià)于 except BaseException:
)。
兩種情況不應(yīng)該只使用excpet
塊:
如果異常處理的代碼會(huì)打印或者記錄log;至少讓用戶知道發(fā)生了一個(gè)錯(cuò)誤。
-
如果代碼需要做清理工作,使用
raise..try…finally
能很好處理這種情況并且能讓異常繼續(xù)上浮。- 當(dāng)給捕捉的異常綁定一個(gè)名字時(shí),推薦使用在Python 2.6中加入的顯式命名綁定語法:
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
為了避免和原來基于逗號分隔的語法出現(xiàn)歧義,Python3只支持這一種語法。
當(dāng)捕捉操作系統(tǒng)的錯(cuò)誤時(shí),推薦使用Python 3.3 中errno內(nèi)定數(shù)值指定的異常等級。
另外,對于所有的
try/except
語句塊,在try語句中只填充必要的代碼,這樣能避免掩蓋掉bug。
Yes:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
No:
try:
# Too broad!
return handle_value(collection[key])
except KeyError:
# Will also catch KeyError raised by handle_value()
return key_not_found(key)
- 當(dāng)代碼片段局部使用了某個(gè)資源的時(shí)候,使用with 表達(dá)式來確保這個(gè)資源使用完后被清理干凈。用
try/finally
也可以。 - 無論何時(shí)獲取和釋放資源,都應(yīng)該通過單獨(dú)的函數(shù)或方法調(diào)用上下文管理器。舉個(gè)例子:
Yes:
with conn.begin_transaction():
do_stuff_in_transaction(conn)
No:
with conn:
do_stuff_in_transaction(conn)
第二個(gè)例子沒有提供任何信息去指明__enter__
和__exit__
方法在事務(wù)之后做出了關(guān)閉連接之外的其他事情。這種情況下,明確指明非常重要。
- 返回的語句保持一致。函數(shù)中的返回語句都應(yīng)該返回一個(gè)表達(dá)式,或者都不返回。如果一個(gè)返回語句需要返回一個(gè)表達(dá)式,那么在沒有值可以返回的情況下,需要用
return None
顯式指明,并且在函數(shù)的最后顯式指定一條返回語句(如果能跑到那的話)。
Yes:
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)
No:
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之前的版本可以不用考慮這個(gè)規(guī)則。 - 使用
.startswith()
和.endswith()
代替通過字符串切割的方法去檢查前綴和后綴。
startswith()
和endswith()
更干凈,出錯(cuò)幾率更小。比如:
Yes: if foo.startswith('bar'):
No: if foo[:3] == 'bar':
- 對象類型的比較應(yīng)該用
isinstance()
而不是直接比較type。
Yes: if isinstance(obj, int):
No: if type(obj) is type(1):
當(dāng)檢查一個(gè)對象是否為string
類型時(shí),記住,它也有可能是unicode string
!在Python2中,str
和unicode
都有相同的基類:basestring
,所以你可以這樣:
if isinstance(obj, basestring):
注意,在Python3中,unicode
和basestring
都不存在了(只有str
)并且bytes
類型的對象不再是string
類型的一種(它是整數(shù)序列)
- 對于序列來說(
strings
,lists
,tuples
),可以使用空序列為false的情況。
Yes: if not seq:
if seq:
No: if len(seq):
if not len(seq):
- 書寫字符串時(shí)不要依賴單詞結(jié)尾的空格,這樣的空格在視覺上難以區(qū)分,有些編輯器會(huì)自動(dòng)去掉他們(比如 reindent.py)。
- 不要用 == 去和True或者False比較:
Yes: if greeting:
No: if greeting == True:
Worse: if greeting is True:
功能注釋
隨著PEP 484的接受,功能注釋的樣式規(guī)則正在發(fā)生變化。
為了向前兼容,Python 3代碼中的函數(shù)注釋應(yīng)該優(yōu)選使用PEP 484語法。(上一節(jié)中有一些注釋的格式化建議。)
不再鼓勵(lì)先前在本PEP中推薦的注釋樣式的實(shí)驗(yàn)。
但是,在stdlib之外, 現(xiàn)在鼓勵(lì)在PEP 484規(guī)則內(nèi)進(jìn)行實(shí)驗(yàn)。例如,使用PEP 484樣式類型注釋標(biāo)記大型第三方庫或應(yīng)用程序,查看添加這些注釋的容易程度,并觀察它們的存在是否增加了代碼的可理解性。
Python標(biāo)準(zhǔn)庫在采用這樣的注釋時(shí)應(yīng)該保守,但是它們的使用允許用于新代碼和大型重構(gòu)。
對于想要對函數(shù)注釋進(jìn)行不同使用的代碼,建議對表單進(jìn)行注釋:
# type: ignore
如果這個(gè)靠近文件頂部; 這告訴類型檢查器忽略所有注釋。(在PEP 484中可以找到更細(xì)粒度的禁用類型檢查器投訴的方法。)
像linters一樣,類型檢查器是可選的,單獨(dú)的工具。默認(rèn)情況下,Python解釋器不應(yīng)由于類型檢查而發(fā)出任何消息,并且不應(yīng)基于注釋更改其行為。
不想使用類型檢查器的用戶可以自由地忽略它們。但是,預(yù)計(jì)第三方庫包的用戶可能希望在這些包上運(yùn)行類型檢查器。為此, PEP 484建議使用存根文件:.pyi文件由類型檢查程序讀取,而不是相應(yīng)的.py文件。存根文件可以與庫一起分發(fā),也可以通過類型化倉庫[5]單獨(dú)(與庫作者的許可)一起分發(fā)。
參考
[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] Donald Knuth's The TeXBook, pages 195 and 196.
[4] http://www.wikipedia.com/wiki/CamelCase
[5] Typeshed repo https://github.com/python/typeshed
[6] | 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