前言:為了讓不同編碼習(xí)慣的開發(fā)者更好的協(xié)作配合,并且形成良好的基礎(chǔ)編碼規(guī)范與風(fēng)格,我們以 PEP8 為基礎(chǔ),修改了陳舊的規(guī)則,豐富了示例,并整理了工作中常見的不規(guī)范操作,最終形成此 Python 編碼規(guī)范與風(fēng)格。
一、編碼風(fēng)格
二、編碼規(guī)范
2.1 三目運算符
2.2 None 條件的判斷
2.3 lambda 匿名函數(shù)
2.4 異常
2.5 條件表達式
2.6 True/False 布爾運算
2.7 列表推導(dǎo)式
2.8 函數(shù)
2.9 變量
工具與配置
flake8
pylint
black
EditorConfig
注意事項
本規(guī)范適用于所有使用 Python 語言作為開發(fā)語言的軟件產(chǎn)品。
由于 Python2 在 2020 年停止維護,建議新增的項目使用 Python3.6+,可以使用到更多的高級特性。如果項目有兼容性需求需要支持老版本 Python 的,那么不涉及的特性可以忽略。本規(guī)范的示例采用符合 Python3.6+ 的語法。
- 推薦(Preferable):用戶理應(yīng)采用,但如有特殊情況,可以不采用;
- 可選(Optional):用戶可參考,自行決定是否采用;
未明確指明的則默認(rèn)為 必須(Mandatory)。
一、編碼風(fēng)格
規(guī)范地代碼布局有助于幫助開發(fā)者更容易地理解業(yè)務(wù)邏輯。
1.1 縮進
1.1.1 【必須】 對于每級縮進,統(tǒng)一要求使用 4 個空格,而非tab鍵。pylint:bad-indentation
.
1.1.2 【必須】 續(xù)行,要求使用括號等定限界符,并且需要垂直對齊。
正確示范
# 與定界(括號)符對齊
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 換行并增加4個額外的空格(一級縮進)
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)
錯誤示范
# 當(dāng)不使用垂直對齊時,第一行不允許加參數(shù)
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 下面這種情況,需要增加額外的縮進,否則無法區(qū)分代碼所在的縮進級別
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
1.1.3 【推薦】 如果包含定界符(括號,中括號,大括號)的表達式跨越多行,那么定界符的擴回符, 可以放置與最后一行的非空字符對齊或者與構(gòu)造多行的開始第一個字符對齊。
正確示范
# 與最后一行的非空字符對齊
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
# 或者與開始構(gòu)造多行的第一個字符對齊
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
1.1.4 【推薦】 對于會經(jīng)常改動的函數(shù)參數(shù)、列表、字典定義,建議每行一個元素,并且每行增加一個 ,
。
正確示范
yes = ('y', 'Y', 'yes', 'TRUE', 'True', 'true', 'On', 'on', '1') # 基本不再改變
kwlist = [
'False',
'None',
'True',
'and',
'as',
'assert',
...
'yield', # 最后一個元素也增加一個逗號 ,方便以后diff不顯示此行
]
person = {
'name': 'bob',
'age': 12, # 可能經(jīng)常增加字段
}
錯誤示范
# 關(guān)鍵字會不斷增加,每個元素都換行
kwlist = ['False', 'None', 'True', 'and', 'as',
'assert', 'async', ...
]
person = {'name': 'bob', 'age': 12}
1.1.5 【可選】 對于 if 判斷,一般來說盡量不要放置過多的判斷條件。換行時增加 4 個額外的空格。pycodestyle:E129 visually indented line with same indent as next logical line
.
備注:PEP8 沒有明確規(guī)定,以下幾種都是允許的。建議使用前面 2 種方法,后 2 種會與已有的開源工具沖突。
正確示范
# 更推薦:在續(xù)行中,增加額外的縮進級別。允許 and 操作符在前
if (this_is_one_thing
and that_is_another_thing):
do_something()
# 更推薦:在續(xù)行中,增加額外的縮進級別
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 允許:與定界符(括號)對齊,不需要額外的縮進
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()
1.2 每行最大長度
1.2.1 【必須】 每行最多不超過 120
個字符。每行代碼最大長度限制的根本原因是過長的行會導(dǎo)致閱讀障礙,使得縮進失效。pylint:line-too-long
.
除了以下兩種情況例外:
如果需要一個長的字符串,可以用括號實現(xiàn)隱形連接。
正確示范
x = ('This will build a very long long '
'long long long long long long string')
1.3 空白符
1.3.1 【必須】 在表達式的賦值符號、操作符左右至少有一個空格。
正確示范
x = y + 1
錯誤示范
x=y+1
x = y+1
1.3.2 【必須】 禁止行尾空白。pylint:trailing-whitespace
.
行尾空白雖然不會造成功能性異常,但是這些空白字符會被源碼管理系統(tǒng)標(biāo)記出來顯示為差異,對開發(fā)人員造成困惱。
正確示范
# YES: 尾部沒有空白符號
+ para = {}
+ para = {} # comment
錯誤示范
# No: 結(jié)尾存在多余空白符號
- para = {}·····
- para = {} # comment······
1.4 操作符
1.4.1 【推薦】 Python 沒有三目操作符,對于二目操作符來說,操作符允許在換行符之后出現(xiàn)。
備注:pycodestyle 工具與此條目相反,PEP8 推薦操作符在這之前,更具備可讀性。PEP8: Should a Line Break Before or After a Binary Operator?。屏蔽 pycodestyle:W503 line break before binary operator
正確示范
# YES: 易于將運算符與操作數(shù)匹配,可讀性高
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
錯誤示范
# No: 運算符的位置遠離其操作數(shù)
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
1.5 括號
1.5.1 【必須】 tuple 元組不允許逗號結(jié)尾,顯式增加括號規(guī)避。即使一個元素也加上括號。pylint:trailing-comma-tuple
.
行尾的逗號可能導(dǎo)致本來要定義一個簡單變量,結(jié)果變成 tuple 變量。
正確示范
trailingcomma = (['f'],)
return (1,)
錯誤示范
trailingcomma = ['f'], # tuple
return 1,
1.6 空行
1.6.1 【必須】 模塊中的一級函數(shù)和類定義之間,需要空兩行
。pycodestyle:E302 expected 2 blank lines
.
1.6.2 【必須】 類中函數(shù)定義之間,空一行
。pycodestyle:E302 expected 1 blank line
.
1.6.3 【必須】 源文件須使用且僅使用 一個換行符
作為結(jié)尾。pylint:missing-final-newline
, trailing-newlines
.
1.6.4 【必須】 通常每個語句應(yīng)該獨占一行。pylint:multiple-statements
.
如果測試結(jié)果與測試語句在一行放得下,你也可以將它們放在同一行。如果是 if
語句,只有在沒有 else
時才能這樣做。特別地,絕不要對 try
/except
這樣做,因為 try
和 except
不能放在同一行。
正確示范
if foo:
bar(foo)
else:
baz(foo)
try:
bar(foo)
except ValueError:
baz(foo)
錯誤示范
if foo: bar(foo)
else: baz(foo)
try: bar(foo)
except ValueError: baz(foo)
try:
bar(foo)
except ValueError: baz(foo)
1.6.5 【推薦】 可以在代碼段中的空一行
來區(qū)分不同業(yè)務(wù)邏輯塊。
'''This is the example module.
This module does stuff.
'''
import os
def foo():
pass
class MyClass():
def __init__(self):
pass
def foo(self):
pass
class AnotherClass(object):
'''Another class.
This is some comments for another class
'''
def __init__(self,
a,
b):
if a > b:
self._min = b
self._max = a
else:
self._min = a
self._max = b
self._gap = self._max = self._min
def foo(self):
pass
1.7 源文件編碼
1.7.1 【必須】 源文件編碼需統(tǒng)一使用 UTF-8
編碼,以下內(nèi)容需要增加到每一個 python 文件的頭部。
# -*- coding: utf-8 -*-
1.7.2 【必須】 ** 避免不同操作系統(tǒng)對文件換行處理的方式不同,一律使用 LF
**。pylint:mixed-line-endings
, unexpected-line-ending-format
.
1.8 Shebang
1.8.1 【必須】 程序的 main 文件應(yīng)該以 #!/usr/bin/env python2
或者 #!/usr/bin/env python3
開始, 可以同時支持 Python2、Python3 的#!/usr/bin/env python
。
非程序入口的文件不應(yīng)該出現(xiàn) Shebang 。
正確示范
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
1.9 模塊引用 (import)
1.9.1 【必須】 每個導(dǎo)入應(yīng)該獨占一行。pylint:multiple-imports
.
正確示范
import os
import sys
錯誤示范
import os, sys
1.9.2 【必須】 導(dǎo)入總應(yīng)該放在文件頂部,位于模塊注釋和文檔字符串之后,模塊全局變量和常量之前。pylint:wrong-import-order
.
導(dǎo)入應(yīng)該按照從最通用到最不通用的順序分組,每個分組之間,需要空一行:
每種分組中,建議每個模塊的完整包路徑按 字典序 排序,并忽略大小寫。
正確示范
import foo
from foo import bar
from foo.bar import baz
from foo.bar import Quux
from Foob import ar
1.9.3 【必須】 避免使用 from <module> import *
,因為可能會造成命名空間的污染。pylint:wildcard-import
.
1.9.4 【必須】 禁止導(dǎo)入了模塊卻不使用它。pylint:unused-import
.
正確示范
import os # used
dir_path = os.path.abspath('.')
錯誤示范
import os # unused !
# dir_path = os.path.abspath('.')
1.10 模塊中的魔術(shù)變量 (dunders)
1.10.1 【必須】 對于兩個 _
開頭和兩個 _
結(jié)尾的變量, 如 __all__
,__author__
,__version__
等,應(yīng)該放在模塊文檔之后, 其他模塊導(dǎo)入之前(__future__
除外)。
1.10.2 【必須】 Python 要求 future
導(dǎo)入必須出現(xiàn)在其他模塊導(dǎo)入之前。pylint:misplaced-future
.
正確示范
# -*- coding: utf-8 -*-
#
# Copyright @ 2020 Tencent.com
'''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
1.11 注釋
有效的注釋有助于幫助開發(fā)者更快地理解代碼,模塊,函數(shù),方法,以及行內(nèi)注釋的都有各自的風(fēng)格。
1.11.1 【必須】 所有#
開頭的注釋,必須與所在的代碼塊同級,并置放在代碼之上。pycodestyle:E262 inline comment should start with '# '
.
1.11.2 【必須】 注釋的每一行都應(yīng)該以#
和一個空格開頭。pycodestyle:E266 too many leading '#' for block comment
, E262 inline comment should start with '# '
.
1.11.3 【必須】 行內(nèi)注釋#
與代碼離開至少 2 個空格。pycodestyle:E261 at least two spaces before inline comment
.
1.11.4 【必須】 塊注釋:對于復(fù)雜的操作,可以在代碼之前寫若干行注釋,對簡單的代碼,可以放在行內(nèi)。與代碼離開至少 2 個空格。
正確示范
# this is a very complex operation, please
# read this carefully
if i & (i-1) == 0:
# do my job ...
# 單行注釋,為可讀性,至少離開代碼2個空格
x = x + 1 # Compensate for border
1.11.5 【必須】 TODO 注釋需要加上名字。
TODO 注釋應(yīng)該在所有開頭處包含 TODO
字符串,緊跟著是用括號括起來的你的名字, email 地址或其它標(biāo)識符,然后是一個可選的冒號。接著必須有一行注釋,解釋要做什么。主要目的是為了有一個統(tǒng)一的 TODO 格式,這樣添加注釋的人就可以搜索到 (并可以按需提供更多細節(jié))。寫了 TODO 注釋并不保證寫的人會親自解決問題。當(dāng)你寫了一個 TODO,請注上你的名字。
為臨時代碼使用 TODO 注釋,它是一種短期解決方案。常見的 IDE 在提交代碼時, 會檢查變更中包含了 TODO 并提醒開發(fā)者,防止提交是忘記還有未完成的代碼。如果 TODO 是將來做某事
的形式,那么請確保包含一個指定的日期或者一個特定的事件(條件)。相同地,也可以留下 FIXME
, NOTES
注釋。
正確示范
# TODO(zhangsan): Change this to use relations.
# FIXME(zhangsan@xx.com): Please fix me here.
# NOTES(zhangsan): This is some notes.
1.12 文檔字符串
Docstring 文檔字符串提供了將文檔與 Python 模塊,函數(shù),類和方法相關(guān)聯(lián)的便捷方法。
def foobar():
'''Return a foobang
Optional plotz says to frobnicate the bizbaz first.
'''
PEP 257 全面描述了文檔字符串的風(fēng)格。
1.12.1 【推薦】 需對外發(fā)布的 public 模塊,函數(shù),類,方法等需要包含文檔字符串。內(nèi)部使用的方法,函數(shù)等,要求使用簡單的注釋描述功能。pylint:missing-module-docstring
, missing-class-docstring
, missing-function-docstring
.
一個函數(shù)或方法,如果可以直接被其他開發(fā)者使用,需要提供文檔明確其含義,需要指出輸入,輸出,以及異常內(nèi)容。
1.12.2 【必須】 第一行應(yīng)為文檔名,空一行后,輸入文檔描述。
1.12.3 【推薦】 在使用文檔字符串時,推薦使用 reStructuredText
風(fēng)格類型。
正確示范
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
'''Fetches rows from a Bigtable.
Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.
:param big_table: An open Bigtable Table instance.
:param keys: A sequence of strings representing the key of each table row
to fetch.
:param other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.
:return: A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}
If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.
:raises ValueError: if `keys` is empty.
:raises IOError: An error occurred accessing the bigtable.Table object.
'''
pass
1.12.4 【推薦】 類應(yīng)該在其定義下有一個用于描述該類的文檔字符串。如果類有公共屬性 (Attributes),那么文檔中應(yīng)該有一個屬性 (Attributes) 段, 并且應(yīng)該遵守和函數(shù)參數(shù)相同的格式。
正確示范
class SampleClass(object):
'''Summary of class here.
Longer class information....
Longer class information....
:ivar likes_spam: A boolean indicating if we like SPAM or not.
:ivar eggs: An integer count of the eggs we have laid.
'''
def __init__(self, likes_spam=False):
'''Inits SampleClass with blah.'''
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
'''Performs operation blah.'''
1.13 類型提示
Python 是動態(tài)語言,在運行時無需指定變量類型。雖然運行時不會執(zhí)行函數(shù)與變量類型注解,但類型提示有助于閱讀代碼、重構(gòu)、靜態(tài)代碼檢查與 IDE 的語法提示。推薦在項目中使用該特性。更多使用可以參考 類型標(biāo)注支持。
類型提示示例代碼
from typing import List
class Container(object):
def __init__(self) -> None:
self.elements: List[int] = []
def append(self, element: int) -> None:
self.elements.append(element)
def greeting(name: str) -> str:
return 'Hello ' + name
# 變量定義
lang: str = 'zh'
success_code: int = 0
1.13.1 【必須】 模塊級變量,類和實例變量以及局部變量的注釋應(yīng)在冒號后面有一個空格。pycodestyle:E231 missing whitespace after ':'
.
1.13.2 【必須】 冒號前不應(yīng)有空格。
1.13.3 【必須】 如果有賦值符,則等號在兩邊應(yīng)恰好有一個空格。pycodestyle:E225 missing whitespace around operator
.
正確示范
code: int = 10
class Point(object):
coords: Tuple[int, int] = (0, 0)
label: str = '<unknown>'
def broadcast(servers: Sequence[Server],
message: str = 'spaces around equality sign') -> None:
pass
錯誤示范
code:int # No space after colon
code : int # Space before colon
class Test(object):
result: int=0 # No spaces around equality sign
1.13.4 【推薦】 當(dāng)使用類型提示出現(xiàn)循環(huán)引用時,可以在導(dǎo)入的頭部使用 if typing.TYPE_CHECKING
, 且對類型注解使用雙引號
或單引號
進行修飾。
正確示范
import typing
if typing.TYPE_CHECKING: # 運行時不導(dǎo)入
# For type annotation
from typing import Any, Dict, List, Sequence # NOQA
from sphinx.application import Sphinx # NOQA
class Parser(docutils.parsers.Parser):
def set_application(self, app: 'Sphinx') -> None: # 同時采用引號
pass
1.14 字符串
1.14.1 【推薦】 即使參數(shù)都是字符串,也要使用 % 操作符或者格式化方法格式化字符串。不過也不能一概而論,你需要在 + 和 % 之間權(quán)衡。
正確示范
# 更推薦
x = f'name: {name}; score: {n}' # Python3.6+ 以上支持
x = 'name: {name}; score: {n}'.format(name=name, n=n)
x = 'name: {name}; score: {n}'.format(**{'name': name, 'n': n})
x = 'name: %(name)s; score: %(n)d' % {'name': name, 'n': n}
# 可接受
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}!'.format(imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
錯誤示范
x = '%s%s' % (a, b) # use + in this case
x = '{}{}'.format(a, b) # use + in this case
x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)
1.14.2 【推薦】 避免在循環(huán)中用 +
和 +=
操作符來累加字符串。由于字符串是不可變的,這樣做會創(chuàng)建不必要的臨時對象,并且導(dǎo)致二次方而不是線性的運行時間。作為替代方案,你可以將每個子串加入列表,然后在循環(huán)結(jié)束后用 .join
連接列表。(也可以將每個子串寫入一個 io.StringIO 緩存中。) pylint:consider-using-join
.
正確示范
items = ['<table>']
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
錯誤示范
employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
1.14.3 【推薦】 在同一個文件中,保持使用字符串引號的一致性。使用單引號'
或者雙引號 '
引用字符串,并在同一文件中一直沿用這種風(fēng)格。當(dāng)字符串中包含單引號或者雙引號時,為提高可讀性,使用另外一種引號,代替轉(zhuǎn)義字符。
正確示范
Python('Why are you hiding your eyes?')
Gollum('I'm scared of lint errors.')
Narrator(''Good!' thought a happy Python reviewer.')
錯誤示范
Python('Why are you hiding your eyes?')
Gollum('The lint. It burns. It burns us.')
Gollum('Always the great lint. Watching. Watching.')
1.14.4 【必須】 如果要引用的字符串為多行時,需要使用雙引號引用字符串。
1.14.5 【必須】 文檔字符串(docstring)必須使用三重雙引號 '''
。
1.14.6 【可選】 避免在代碼中使用三重引號 '''
,因為當(dāng)使用三重引號時,縮進方式與其他部分不一致,容易引起誤導(dǎo)。
正確示范
print('This is much nicer.\n'
'Do it this way.\n')
錯誤示范
print('''This is pretty ugly.
Don't do this.
''')
1.14.7 【推薦】 檢查前綴和后綴時,使用 .startswith()
和 .endswith()
代替字符串切片。
正確示范
if foo.startswith('bar'):
錯誤示范
if foo[:3] == 'bar':
1.15 文件和 sockets
1.15.1 【必須】 在文件和 sockets 結(jié)束時,顯式的關(guān)閉它。
除文件外, sockets 或其他類似文件的對象在沒有必要的情況下打開,會有許多副作用,例如:
- 它們可能會消耗有限的系統(tǒng)資源,如文件描述符。如果這些資源在使用后沒有及時歸還系統(tǒng),那么用于處理這些對象的代碼會將資源消耗殆盡。
- 持有文件將會阻止對于文件的其他諸如移動、刪除之類的操作。
- 僅僅是從邏輯上關(guān)閉文件和 sockets,那么它們?nèi)匀豢赡軙黄涔蚕淼某绦蛟跓o意中進行讀或者寫操作。只有當(dāng)它們真正被關(guān)閉后,對于它們嘗試進行讀或者寫操作將會拋出異常,并使得問題快速顯現(xiàn)出來。
而且,幻想當(dāng)文件對象析構(gòu)時,文件和 sockets 會自動關(guān)閉,試圖將文件對象的生命周期和文件的狀態(tài)綁定在一起的想法,都是不現(xiàn)實的。因為有如下原因:
- 沒有任何方法可以確保運行環(huán)境會真正的執(zhí)行文件的析構(gòu)。不同的 Python 實現(xiàn)采用不同的內(nèi)存管理技術(shù),比如延時垃圾處理機制。延時垃圾處理機制可能會導(dǎo)致對象生命周期被任意無限制的延長。
- 對于文件意外的引用,會導(dǎo)致對于文件的持有時間超出預(yù)期 (比如對于異常的跟蹤,包含有全局變量等)。
1.15.2 【推薦】 推薦使用 with
語句管理文件。
with open('hello.txt') as hello_file:
for line in hello_file:
print(line)
對于不支持使用 with
語句的類似文件的對象,使用 contextlib.closing()
:
import contextlib
with contextlib.closing(urllib.urlopen('http://www./')) as front_page:
for line in front_page:
print(line)
Legacy AppEngine 中 Python 2.5 的代碼如使用 with
語句,需要添加 from __future__ import with_statement
.
1.16 訪問控制
在 Python 中,對于瑣碎又不太重要的訪問函數(shù),應(yīng)該直接使用公有變量來取代它們,這樣可以避免額外的函數(shù)調(diào)用開銷。當(dāng)添加更多功能時,也可以用屬性 (property) 來保持語法的一致性。
1.16.1 【推薦】 如果訪問屬性后需要復(fù)雜的邏輯處理,或者變量的訪問開銷很顯著, 那么應(yīng)該使用像 get_foo()
和 set_foo()
這樣的函數(shù)調(diào)用。如果之前的代碼行為已經(jīng)通過屬性 (property) 訪問,那么就不要將新的訪問函數(shù)與屬性綁定。否則,任何試圖通過老方法訪問變量的代碼就沒法運行,使用者也就會意識到復(fù)雜性發(fā)生了變化。(如果可以重構(gòu)這個代碼是最好的了)
1.17 Main
即使是一個打算被用作腳本的文件,也應(yīng)該是可導(dǎo)入的。并且簡單的導(dǎo)入不應(yīng)該導(dǎo)致這個腳本的主功能 (main functionality) 被執(zhí)行,這是一種副作用。主功能應(yīng)該放在一個 main () 函數(shù)中。
1.17.1 【必須】 所有的文件都應(yīng)該可以被導(dǎo)入。對不需要作為程序入口地方添加 if __name__ == '__main__'
。
在 Python 中,pydoc 以及單元測試要求模塊必須是可導(dǎo)入的。你的代碼應(yīng)該在執(zhí)行主程序前總是檢查 if __name__ == '__main__'
, 這樣當(dāng)模塊被導(dǎo)入時主程序就不會被執(zhí)行。所有的頂級代碼在模塊導(dǎo)入時都會被執(zhí)行。要小心不要去調(diào)用函數(shù),創(chuàng)建對象,或者執(zhí)行那些不應(yīng)該在使用 pydoc 時執(zhí)行的操作。
正確示范
def main():
...
if __name__ == '__main__':
main()
1.18 命名
module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.
應(yīng)該避免的名稱:
- 雙下劃線開頭并結(jié)尾的名稱 (Python 保留,例如__init__)。
1.18.1 【推薦】 命名約定規(guī)則如下:pylint:invalid-name
.
- 所謂
內(nèi)部(Internal)
表示僅模塊內(nèi)可用,或者,在類內(nèi)是保護或私有的。 - 用單下劃線
(_)
開頭表示模塊變量或函數(shù)是 protected 的 (使用 from module import * 時不會包含)。 - 用雙下劃線
(__)
開頭的實例變量或方法表示類內(nèi)私有。 - 將相關(guān)的類和頂級函數(shù)放在同一個模塊里。不像 Java,沒必要限制一個類一個模塊。
- 對類名使用大寫字母開頭的單詞 (如 CapWords, 即 Pascal 風(fēng)格),但是模塊名應(yīng)該用小寫加下劃線的方式 (如 lower_with_under.py)。盡管已經(jīng)有很多現(xiàn)存的模塊使用類似于 CapWords.py 這樣的命名,但現(xiàn)在已經(jīng)不鼓勵這樣做,因為如果模塊名碰巧和類名一致,這會讓人困擾。
Python 之父 Guido 推薦的規(guī)范:
Type | Public | Internal |
---|
Modules | lower_with_under | _lower_with_under |
Packages | lower_with_under |
|
Classes | CapWords | _CapWords |
Exceptions | CapWords |
|
Functions | lower_with_under() | _lower_with_under() |
Global/Class Constants | CAPS_WITH_UNDER | _CAPS_WITH_UNDER |
Global/Class Variables | lower_with_under | _lower_with_under |
Instance Variables | lower_with_under | _lower_with_under (protected) or __lower_with_under (private) |
Method Names | lower_with_under() | _lower_with_under() (protected) or __lower_with_under() (private) |
Function/Method Parameters | lower_with_under |
|
Local Variables | lower_with_under |
|
二、編碼規(guī)范
2.1 三目運算符
2.1.1 【必須】 三目操作符判斷,python 不支持三目運算符,但可使用如下方式,禁止使用復(fù)雜難懂的邏輯判斷。
正確示范
x = a if a >= b else b
錯誤示范
x = a >= b and a or b
2.2 None 條件的判斷
2.2.1 【必須】 為提升可讀性,在判斷條件中應(yīng)使用 is not
,而不使用 not ... is
。pycodestyle:E714 test for object identity should be 'is not'
.
正確示范
if foo is not None:
錯誤示范
if not foo is None:
2.3 lambda 匿名函數(shù)
2.3.1 【必須】 使用 def 定義簡短函數(shù)而不是使用 lambda。pycodestyle:E731 do not assign a lambda expression, use a def
.
使用 def 的方式有助于在 trackbacks 中打印有效的類型信息,明確使用 f
函數(shù)而不是一個 lambda 的調(diào)用。
正確示范
def f(x):
return 2 * x
錯誤示范
f = lambda x: 2 * x
2.4 異常
2.4.1 【必須】 異常類繼承自 Exception
,而不是 BaseException
。
2.4.2 【必須】 使用新版本拋出異常的方式,禁止使用廢棄的方式。pycodestyle:W602 deprecated form of raising exception
.
正確示范
raise ValueError('message')
錯誤示范
raise ValueError, 'message'
2.4.3 【必須】 捕獲異常時,需要指明具體異常,而不是捕獲所有異常。除非已經(jīng)在當(dāng)前線程的最外層(記得還是要打印一條 traceback)。pylint:broad-except
, bare-except
.
正確示范
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
try:
do_something()
except Exception as ex: # pylint: disable=broad-except
log.exception(ex) # 應(yīng)將錯誤堆棧打印到日志文件中,以供后續(xù)排查。
handle_exception_or_not() # 除打印日志外,還可以進一步處理如清理資源等,可選。
錯誤示范
try:
import platform_specific_module
except Exception: # broad-except
platform_specific_module = None
try:
do_something()
except Exception: # 框架等未明確異常場景,建議增加 traceback 打印
pass
2.4.4 【推薦】 建議在代碼中用異常替代函數(shù)的錯誤返回碼。
正確示范
def write_data():
if check_file_exist():
do_something()
else:
raise FileNotExist()
錯誤示范
def write_data():
if check_file_exist():
do_something()
return 0
else:
return FILE_NOT_EXIST
2.4.5 【推薦】 在 except
子句中重新拋出原有異常時,不能用 raise ex
,而是用 raise
。
正確示范
try:
raise MyException()
except MyException as ex:
try_handle_exception()
raise # 可以保留原始的 traceback
try:
raise MyException()
except MyException as ex:
log.exception(ex)
raise AnotherException(str(ex)) # 允許的,建議保留好之前的異常棧信息,用于定位問題
錯誤示范
try:
raise MyException()
except MyException as ex:
try_handle_exception()
raise ex # 異常棧信息從這里開始,原始的raise異常棧信息消息
2.4.6 【推薦】 所有 try
/except
子句的代碼要盡可的少,以免屏蔽其他的錯誤。
正確示范
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
錯誤示范
try:
# 范圍太廣
return handle_value(collection[key])
except KeyError:
# 會捕捉到 handle_value() 中的 KeyError
return key_not_found(key)
2.5 條件表達式
2.5.1 【推薦】 函數(shù)或者方法在沒有返回時要明確返回 None
。pylint:inconsistent-return-statements
.
正確示范
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)
2.5.2 【推薦】 對于未知的條件分支或者不應(yīng)該進入的分支,建議拋出異常,而不是返回一個值(比如說 None
或 False
)。
正確示范
def f(x):
if x in ('SUCCESS',):
return True
else:
raise MyException() # 如果一定不會走到的條件,可以增加異常,防止將來未知的語句執(zhí)行。
錯誤示范
def f(x):
if x in ('SUCCESS',):
return True
return None
2.5.3 【可選】 if
與 else
盡量一起出現(xiàn),而不是全部都是 if
子句。
正確示范
if condition:
do_something()
else:
# 增加說明注釋
pass
if condition:
do_something()
# 這里增加注釋說明為什么不用寫else子句
# else:
# pass
錯誤示范
if condition:
do_something()
if another_condition: # 不能確定是否筆誤為 elif ,還是開啟全新一個if條件
do_another_something()
else:
do_else_something()
2.6 True/False 布爾運算
2.6.1 【必須】 不要用 ==
與 True
、 False
進行布爾運算。pylint:singleton-comparison
.
正確示范
if greeting:
pass
錯誤示范
if greeting == True:
pass
if greeting is True: # Worse
pass
2.6.2 【必須】 對序列(字符串、列表 、元組),空序列為 false 的情況。pylint:len-as-condition
.
正確示范
if not seq:
pass
if seq:
pass
錯誤示范
if len(seq):
pass
if not len(seq):
pass
2.7 列表推導(dǎo)式
2.7.1 【必須】 禁止超過 1 個 for 語句或過濾器表達式,否則使用傳統(tǒng) for
循環(huán)語句替代。
正確示范
number_list = [1, 2, 3, 10, 20, 55]
odd = [i for i in number_list if i % 2 == 1]
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
錯誤示范
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] # for 語句
2.7.2 【推薦】 列表推導(dǎo)式適用于簡單場景。如果語句過長,每個部分應(yīng)該單獨置于一行:映射表達式, for 語句, 過濾器表達式。
正確示范
fizzbuzz = []
for n in range(100):
if n % 3 == 0 and n % 5 == 0:
fizzbuzz.append(f'fizzbuzz {n}')
elif n % 3 == 0:
fizzbuzz.append(f'fizz {n}')
elif n % 5 == 0:
fizzbuzz.append(f'buzz {n}')
else:
fizzbuzz.append(n)
for n in range(1, 11):
print(n)
錯誤示范
# 條件過于復(fù)雜,應(yīng)該采用for語句展開
fizzbuzz = [
f'fizzbuzz {n}' if n % 3 == 0 and n % 5 == 0
else f'fizz {n}' if n % 3 == 0
else f'buzz {n}' if n % 5 == 0
else n
for n in range(100)
]
[print(n) for n in range(1, 11)] # 無返回值
2.8 函數(shù)
2.8.1 【必須】 模塊內(nèi)部禁止定義重復(fù)函數(shù)聲明。pylint:function-redefined
.
禁止重復(fù)的函數(shù)定義。
錯誤示范
def get_x(x):
return x
def get_x(x): # 模塊內(nèi)重復(fù)定義
return x
2.8.2 【必須】 函數(shù)參數(shù)中,不允許出現(xiàn)可變類型變量作為默認(rèn)值。pylint:dangerous-default-value
.
正確示范
def f(x=0, y=None, z=None):
if y is None:
y = []
if z is None:
z = {}
錯誤示范
def f(x=0, y=[], z={}):
pass
def f(a, b=time.time()):
pass
2.9 變量
2.9.1 【必須】 禁止定義了變量卻不使用它。pylint:unused-variable
.
在代碼里到處定義變量卻沒有使用它,不完整的代碼結(jié)構(gòu)看起來像是個代碼錯誤。即使沒有使用,但是定義變量仍然需要消耗資源,并且對閱讀代碼的人也會造成困惑,不知道這些變量是要做什么的。
正確示范
def get_x_plus_y(x, y):
return x + y
錯誤示范
some_unused_var = 42
# 定義了變量不意味著就是使用了
y = 10
y = 5
# 對自身的操作并不意味著使用了
z = 0
z = z + 1
# 未使用的函數(shù)參數(shù)
def get_x(x, y):
return x
2.9.2 【推薦】 使用雙下劃線 __
來代表不需要的變量,單下劃線 _
容易與 gettext()
函數(shù)的別名沖突。
正確示范
path = '/tmp/python/foobar.txt'
dir_name, __ = os.path.split(path)
工具與配置
flake8
flake8
是一個結(jié)合了 pycodestyle,pyflakes,mccabe 檢查 Python 代碼規(guī)范的工具。
使用方法:
flake8 {source_file_or_directory}
在項目中創(chuàng)建 setup.cfg
或者 tox.ini
或者 .flake8
文件,添加 [flake8]
部分。
推薦的配置文件如下:
[flake8]
ignore =
;W503 line break before binary operator
W503,
;E203 whitespace before ':'
E203,
; exclude file
exclude =
.tox,
.git,
__pycache__,
build,
dist,
*.pyc,
*.egg-info,
.cache,
.eggs
max-line-length = 120
如果需要屏蔽告警可以增加行內(nèi)注釋 # noqa
,例如:
example = lambda: 'example' # noqa: E731
pylint
pylint
是一個能夠檢查編碼質(zhì)量、編碼規(guī)范的工具。
配置項較多,單獨一個配置文件配置,詳情可查閱:.pylintrc
使用方法:
pylint {source_file_or_directory}
如果遇到一些實際情況與代碼沖突的,可以在行內(nèi)禁用相關(guān)檢查,例如 :
try:
do_something()
except Exception as ex: # pylint: disable=broad-except
pass
如果需要對多行的進行禁用規(guī)則,可以配套使用 # pylint: disable=具體錯誤碼
/# pylint: enable=具體錯誤碼
。
# pylint: disable=invalid-name
這里的代碼塊會被忽略相關(guān)的告警
app = Flask(__name__)
# pylint: enable=invalid-name
black
black
是一個官方的 Python 代碼格式化工具。
使用方法:
black {source_file_or_directory}
如果不想格式化部分代碼,可以配套使用 # fmt: off
/# fmt: on
臨時關(guān)閉格式化。
# fmt: off
在這的代碼不會被格式化
# fmt: on
EditorConfig
EditorConfig 可以幫助開發(fā)同一項目下的跨多 IDE 的開發(fā)人員保持一致編碼風(fēng)格。
在項目的根目錄下放置.editorconfig
文件,可以讓編輯器規(guī)范文件對格式。參考配置如下:
# https://
root = true
[*]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
end_of_line = lf
[*.py]
max_line_length = 120
[*.bat]
indent_style = tab
end_of_line = crlf
[LICENSE]
insert_final_newline = false
[Makefile]
indent_style = tab
支持常見的 IDE ,配置說明及 IDE 的支持情況可參考官網(wǎng) 。