App下載

Google內(nèi)部Python代碼風(fēng)格指南(中文版)

猿友 2021-03-26 17:44:46 瀏覽數(shù) (2529)
反饋

Python 是谷歌主要使用的動(dòng)態(tài)語(yǔ)言,本風(fēng)格指導(dǎo)列舉了使用 Python 編程時(shí)應(yīng)該做和不該做的事項(xiàng)(dos & don'ts)

1 背景

為了幫助你正確地組織代碼,我們編寫(xiě)了一個(gè) Vim 的設(shè)置文件.對(duì)于 Emacs,默認(rèn)設(shè)置即可.

許多團(tuán)隊(duì)使用 yapf 自動(dòng)格式工具來(lái)避免格式爭(zhēng)議

2 Python語(yǔ)言規(guī)則

2.1 Lint

對(duì)代碼使用pylint

2.1.1Definition(以下都譯為定義)

pylint 是一個(gè)用于在 Python 代碼中發(fā)現(xiàn)bug和代碼風(fēng)格問(wèn)題的工具,,pylint 查找那些常在非動(dòng)態(tài)語(yǔ)言(例如 C 或 C++)編譯器中捕獲的問(wèn)題.由于 Python 是動(dòng)態(tài)語(yǔ)言,一些警告可能不正確,不過(guò)應(yīng)該非常少有錯(cuò)誤警告.

2.1.2 Pros

能夠發(fā)現(xiàn)一些易被遺漏的錯(cuò)誤,類(lèi)似拼寫(xiě)錯(cuò)誤,調(diào)用早于聲明等等.

2.1.3 Cons

pylint 并不完美,為了更好的利用工具,我們有時(shí)候需要

a. Write around it(適配上下文風(fēng)格)

b. 壓制一些警告

c. 優(yōu)化工具

2.1.4 Decision(以下都譯為建議)

確保對(duì)代碼應(yīng)用 pylint

如果一些警告是不合適的,就抑制這些警告,這是為了讓其他警告不會(huì)被隱藏.為了壓制警告,可以設(shè)置行級(jí)別的注釋:

dict = 'something awful'  # Bad Idea... pylint: disable=redefined-builtin

pylint 警告包含標(biāo)識(shí)名(empty-docstring),谷歌專(zhuān)有的警告以 g-開(kāi)頭.

如果抑制警告的原因在標(biāo)識(shí)名稱(chēng)中表述不夠清晰,請(qǐng)額外添加注解.

用這種方式來(lái)抑制警告的優(yōu)點(diǎn)是我們能夠簡(jiǎn)單地找到抑制的警告并且重新訪問(wèn)這些警告.

可以通過(guò)下述方式來(lái)獲得 pylint 警告列表:

pylint --list-msgs

用下述方式來(lái)獲取某個(gè)特定消息的更多具體信息:

pylint --help-msg=C6409

優(yōu)先使用 pylint: disable 而非舊方法(pylint: disable-msg)如果要抑制由于參數(shù)未使用的警告,可以在函數(shù)開(kāi)頭 del,并注釋為什么要?jiǎng)h除這些未使用參數(shù),僅僅一句"unused"是不夠的:

def viking_cafe_order(spam, beans, eggs=None):
    del beans, eggs  # Unused by vikings.
    return spam + spam + spa

其他可以用來(lái)抑制警告的方式包括用 '_' 作為未使用參數(shù)的標(biāo)識(shí),在參數(shù)名前增加'unused_',或者分配這些參數(shù)到 '_'.這些方式是可以的,但是已經(jīng)不鼓勵(lì)繼續(xù)使用.前兩種方式會(huì)影響到通過(guò)參數(shù)名傳參的調(diào)用方式,而最后一種并不能保證參數(shù)確實(shí)未被使用.

2.2 Imports

只在 import 包和模塊的時(shí)候使用 import,而不要應(yīng)用在單獨(dú)的類(lèi)或函數(shù).(這一條對(duì)于 typing_module 有特別的意外)

2.2.1 定義

一個(gè)模塊到另一個(gè)模塊之間共享代碼的復(fù)用性機(jī)制

2.2.2 Pros

命名空間管理約定簡(jiǎn)單,每個(gè)標(biāo)識(shí)的源都一致性地被指明了.例如 x.Obj 表示 Obj 是在模塊x中定義的

2.2.3 Cons

模塊名可能會(huì)有沖突,一些模塊名可能很長(zhǎng),比較不方便

2.2.4 建議

  • import x(當(dāng)x是包或模塊)
  • from x import y (當(dāng)x是包前綴,y是不帶前綴的模塊名)
  • from x import  y as z (當(dāng)有重復(fù)模塊名y或y過(guò)長(zhǎng)不利于引用的時(shí)候)
  • import y as z (僅在非常通用的簡(jiǎn)寫(xiě)的時(shí)候使用例如import numpy as np)

以 sound.effects.echo 為例:

from sound.effects import echo...echo.EchoFilter(input, output, delay=0.7, atten=4)

不要使用相對(duì)引用,即便在同一包內(nèi),也使用完整包名 import,這有助于避免無(wú)意重復(fù) import 包.

從 typing module 和 six.moves module import 不適用上述規(guī)則

2.3 包

每一模塊都要從完整路徑 import

2.3.1 Pros

能夠避免模塊名沖突以及由于模塊搜索路徑與作者預(yù)期不符而造成的錯(cuò)誤引用.讓查找模塊更簡(jiǎn)單.

2.3.2 Cons

讓部署代碼時(shí)有些困難,因?yàn)榘軜?gòu)也需要賦值,不過(guò)對(duì)于現(xiàn)在的部署機(jī)制而言,這其實(shí)不是問(wèn)題.

2.3.3 建議

所有的新代碼都要從完整包名來(lái) import 模塊

import 示例應(yīng)該像這樣:

Yes:

# Reference absl.flags in code with the complete name (verbose).
# 在代碼中使用完整路徑調(diào)用absl.flags
import absl.flagsfrom doctor.who import jodie

FLAGS = absl.flags.FLAGS
# Reference flags in code with just the module name (common).
# 在代碼中只用包名來(lái)調(diào)用flags
from absl import flagsfrom doctor.who import jodie

FLAGS = flags.FLAGS

No:(假設(shè)文件在 doctor/who 中,jodie.py 也在這里)

# Unclear what module the author wanted and what will be imported.  The actual
# import behavior depends on external factors controlling sys.path.
# Which possible jodie module did the author intend to import?
# 不清楚作者想要哪個(gè)包以及最終import的是哪個(gè)包,
# 實(shí)際的import操作依賴(lài)于受到外部參數(shù)控制的sys.path
# 那么哪一個(gè)可能的jodie模塊是作者希望import的呢?
import jodie

不應(yīng)該假設(shè)主代碼所在路徑被包含在 sys.path 中,即使有些時(shí)候可以 work.在上一例代碼中,我們應(yīng)該認(rèn)為 import jodie 指的是 import 一個(gè)叫做 jodie 的第三方包或者頂級(jí)目錄中的 jodie,而非一個(gè)當(dāng)前路徑的 jodie.py

2.4 異常

異常處理是允許使用的,但使用務(wù)必謹(jǐn)慎

2.4.1 定義

異常是一種從正常代碼段控制流中跳出以處理錯(cuò)誤或者其他異常條件的手段.

2.4.2 Pros

正常代碼的控制流時(shí)不會(huì)被錯(cuò)誤處理代碼影響的.異常處理同樣允許在某些情況下,控制流跳過(guò)多段代碼,例如在某一步從N個(gè)嵌入函數(shù)返回結(jié)果而非強(qiáng)行延續(xù)錯(cuò)誤代碼.

2.4.3 Cons

可能會(huì)讓控制流變的難于理解,也比較容易錯(cuò)過(guò)調(diào)用庫(kù)函數(shù)的報(bào)錯(cuò).

2.4.4 建議

異常必定遵循特定條件:

  • 使用 raise MyError('Error message')或者 raise MyError(),不要使用兩段 raise MyError, 'Error message'
  • 當(dāng)內(nèi)置異常類(lèi)合理的時(shí)候,盡量使用內(nèi)置異常.例如:拋出 ValueError 來(lái)表示一個(gè)像是違反預(yù)設(shè)前提(例如傳參了一個(gè)負(fù)數(shù)給要求正數(shù)的情況)的程序錯(cuò)誤發(fā)生.

不要使用 assert 來(lái)片段公共結(jié)構(gòu)參數(shù)值 .assert 是用來(lái)確認(rèn)內(nèi)部計(jì)算正確性也不是用來(lái)表示一些預(yù)期外的事件發(fā)生的.如果異常是后續(xù)處理要求的,用 raise 語(yǔ)句來(lái)處理,例如:

Yes:

def connect_to_next_port(self, minimum):
"""Connects to the next available port.

Args:
    minimum: A port value greater or equal to 1024.

Returns:
    The new minimum port.

Raises:
    ConnectionError: If no available port is found.
"""
if minimum < 1024:
    # Note that this raising of ValueError is not mentioned in the doc
    # string's "Raises:" section because it is not appropriate to
    # guarantee this specific behavioral reaction to API misuse.
    # 注意拋出ValueError這件事是不在docstring中的Raises中提及, 因?yàn)檫@樣并適合保障對(duì)于API誤用的特殊反饋
    raise ValueError('Minimum port must be at least 1024, not %d.' % (minimum,))
port = self._find_next_open_port(minimum)
if not port:
    raise ConnectionError('Could not connect to service on %d or higher.' % (minimum,))
assert port >= minimum, 'Unexpected port %d when minimum was %d.' % (port, minimum)
return port

No:

def connect_to_next_port(self, minimum):
"""Connects to the next available port.

Args:
    minimum: A port value greater or equal to 1024.

Returns:
    The new minimum port.
"""
assert minimum >= 1024, 'Minimum port must be at least 1024.'
port = self._find_next_open_port(minimum)
assert port is not None
return port
  • 庫(kù)或者包可能會(huì)定義各自的異常.當(dāng)這樣做的時(shí)候,必須要繼承一個(gè)已經(jīng)存在的異常類(lèi),異常類(lèi)的名字應(yīng)該以 Error 結(jié)尾,并且不應(yīng)該引入重復(fù)(foo.FooError)
  • 永遠(yuǎn)不要用捕獲全部異常的 except:語(yǔ)句,或者捕獲 Exception 或者 StandardError 除非:Python 在這個(gè)方面容忍度很高,并且 except:語(yǔ)句會(huì)捕獲包括拼寫(xiě)錯(cuò)誤,sys.exit(),Ctrl+C 終止,單元測(cè)試失敗和和所有你并沒(méi)有想到捕獲的其他異常.
  • 再次拋出這個(gè)異常在程序中異常不會(huì)繼續(xù)但是會(huì)被記錄以及消除(例如通過(guò)保護(hù)最外層的方式保護(hù)線程不會(huì)崩潰)的地方創(chuàng)造一個(gè)孤立點(diǎn).
  • 最精簡(jiǎn)try/except表達(dá)式內(nèi)部的代碼量,try 代碼塊里的代碼體量越大,月可能會(huì)在你不希望拋出異常的代碼中拋出異常,進(jìn)而在這種情況下,try/except掩蓋了一個(gè)真實(shí)的異常
  • 使用finally來(lái)執(zhí)行代碼,這些代碼無(wú)論是否有異常在 try代碼塊被拋出都會(huì)被執(zhí)行.這在清理(即關(guān)閉文件)時(shí)非常有用.
  • 當(dāng)捕獲了異常時(shí),用as而不是逗號(hào)分段.
try:
    raise Error()
except Error as error:
    pass

2.5 全局變量

避免全局變量

2.5.1 定義

在模塊級(jí)別或者作為類(lèi)屬性聲明的變量

2.5.2 Pros

有些時(shí)候有用

2.5.3 Cons

在 import 的過(guò)程中,有可能改變模塊行為,因?yàn)樵谀K首次被引入的過(guò)程中,全局變量就已經(jīng)被聲明

2.5.4 建議

避免全局變量

作為技術(shù)變量,模塊級(jí)別的常量是允許并鼓勵(lì)使用的.例如MAX_HOLY_HANDGRENADE_COUNT = 3, 常量必須由大寫(xiě)字母和下劃線組成,參見(jiàn)下方命名規(guī)則

如果需要,全局變量需要在模塊級(jí)別聲明,并且通過(guò)在變量名前加_來(lái)使其對(duì)模塊內(nèi)私有化.外部對(duì)模塊全局變量的訪問(wèn)必須通過(guò)公共模塊級(jí)別函數(shù),參見(jiàn)下方命名規(guī)則

2.6 內(nèi)嵌/局部/內(nèi)部 類(lèi)和函數(shù)

內(nèi)嵌局部函數(shù)或類(lèi)在關(guān)閉局部變量時(shí)是可以的.內(nèi)部類(lèi)意識(shí)可用的.(譯注:這里我的理解是當(dāng)內(nèi)嵌局部函數(shù)或類(lèi)是和局部變量在同一個(gè)封閉作用域內(nèi)是可以的.)

2.6.1 定義

類(lèi)可以在方法,函數(shù),類(lèi)內(nèi)定義.函數(shù)可以在方法或函數(shù)內(nèi)定義.內(nèi)嵌函數(shù)對(duì)封閉作用域的變量具有只讀訪問(wèn)權(quán)限.

2.6.2 Pros

允許定義只在非常有限作用域內(nèi)可用的工具類(lèi)或工具函數(shù).Very ADT-y(??符合抽象數(shù)據(jù)類(lèi)型要求???),通常用于實(shí)現(xiàn)裝飾器

2.6.3 Cons

內(nèi)嵌或局部類(lèi)的實(shí)例是不能被 pickle 的,內(nèi)嵌函數(shù)或類(lèi)是不能被直接測(cè)試的.嵌套會(huì)讓外部函數(shù)更長(zhǎng)并且更難讀懂.

2.6.4 建議

除了一些特別聲明,這些內(nèi)嵌/局部/內(nèi)部類(lèi)和函數(shù)都是可以的.避免內(nèi)嵌函數(shù)或類(lèi)除了需要關(guān)閉一個(gè)局部值的時(shí)候.(譯者理解可能是除了將局部變量封閉在同一個(gè)作用域的情況以外).不要把一個(gè)函數(shù)轉(zhuǎn)為內(nèi)嵌指示為了避免訪問(wèn).在這種情況下,把函數(shù)置于模塊級(jí)別并在函數(shù)名前加_以保證測(cè)試是可以訪問(wèn)該函數(shù)的.

2.7 列表推導(dǎo)和生成器表達(dá)式

在簡(jiǎn)單情況下是可用的

2.7.1 定義

List, Dict 和 Set 推導(dǎo)生成式以及生成器表達(dá)式提供了一個(gè)簡(jiǎn)明有效的方式來(lái)生成容器和迭代器而不需要傳統(tǒng)的循環(huán),map(),filter()或者lambda 表達(dá)式

2.7.2 Pros

簡(jiǎn)單地推導(dǎo)表達(dá)比其他的字典,列表或集合生成方法更加簡(jiǎn)明清晰.生成器表達(dá)式可以很有效率,因?yàn)橥耆苊饬松闪斜?

2.7.3 Cons

負(fù)載的推導(dǎo)表達(dá)式或生成器表達(dá)式很難讀懂

2.7.4 建議

簡(jiǎn)單情況下使用時(shí)可以的.每個(gè)部分(mapping 表達(dá)式,filter 表達(dá)式等)都應(yīng)該在一行內(nèi)完成.多個(gè) for 條款或者 filter 表達(dá)式是不允許的.當(dāng)情況變得很復(fù)雜的適合就使用循環(huán).

Yes:

result = [mapping_expr for value in iterable if filter_expr]

result = [{'key': value} for value in iterable
          if a_long_filter_expression(value)]

result = [complicated_transform(x)
          for x in iterable if predicate(x)]

descriptive_name = [
    transform({'key': key, 'value': value}, color='black')
    for key, value in generate_iterable(some_input)
    if complicated_condition_is_met(key, value)
]

result = []
for x in range(10):
    for y in range(5):
        if x * y > 10:
            result.append((x, y))

return {x: complicated_transform(x)
        for x in long_generator_function(parameter)
        if x is not None}

squares_generator = (x**2 for x in range(10))

unique_names = {user.name for user in users if user is not None}

eat(jelly_bean for jelly_bean in jelly_beans
    if jelly_bean.color == 'black')

No:

result = [complicated_transform(
          x, some_argument=x+1)
          for x in iterable if predicate(x)]

result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]

return ((x, y, z)
        for x in range(5)
        for y in range(5)
        if x != y
        for z in range(5)
        if y != z)

2.8 默認(rèn)迭代器和運(yùn)算符

對(duì)支持默認(rèn)迭代器和云算法的類(lèi)型例如列表,字典和文件等使用它們

2.8.1 定義

容器類(lèi)型(例如字典,列表等)定義了的默認(rèn)的迭代器和成員檢查運(yùn)算符.

Pros

默認(rèn)迭代器和操作符是簡(jiǎn)單有效的,能夠直接不需額外調(diào)用方法地表達(dá)操作.使用默認(rèn)操作符的函數(shù)是通用的.能被用于任何支持這些操作的類(lèi)型.

Cons

不能通過(guò)方法名來(lái)分辨類(lèi)型,例如 has_key()意味著字典,當(dāng)然這也是一種優(yōu)勢(shì).

建議

對(duì)于支持的類(lèi)型諸如列表,字典和文件,使用默認(rèn)迭代器和操作符.內(nèi)置類(lèi)型同樣定義了迭代器方法.優(yōu)先使用這些方法而非那些返回列表的方法.除非能夠確定在遍歷容器的過(guò)程中不會(huì)改變?nèi)萜?不要使用 Python 2 專(zhuān)有迭代方法除非必要.

Yes:

for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
for k, v in six.iteritems(adict): ...

No:

for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
for k, v in dict.iteritems(): ...

2.9 生成器

需要時(shí)使用生成器

2.9.1 定義

生成器函數(shù)返回一個(gè)迭代器,每次執(zhí)行 yield 語(yǔ)句的時(shí)候生成一個(gè)值.在生成一個(gè)值之后,生成器函數(shù)的運(yùn)行被掛起直到需要下一個(gè)值.

2.9.2 Pros

簡(jiǎn)化代碼,因?yàn)榫植孔兞亢涂刂屏髟诿看握{(diào)用時(shí)被保留,生成器相比于一次性生成整個(gè)一個(gè)列表值要更節(jié)省內(nèi)存.

2.9.3 Cons

無(wú)

2.9.4 建議

建議使用.在生成器函數(shù)的文檔字符串中使用"Yields:"而非"Returns:"

2.10 Lambda表達(dá)式

單行代碼時(shí)是可以的

2.10.1 定義

lambda 在一個(gè)表達(dá)式內(nèi)定義了匿名函數(shù),而不在語(yǔ)句里.lambda 表達(dá)式常被用于定義高階函數(shù)(例如 map()和 filter())使用的回調(diào)函數(shù)或者操作符.

2.10.2 Pros

方便

2.10.3 Cons

比局部函數(shù)更難讀懂和 debug,匿名意味著堆棧跟蹤更難懂.表達(dá)性受限因?yàn)?lambda 函數(shù)只包含一個(gè)表達(dá)式

2.10.4 建議

對(duì)于單行代碼而言,可以使用 lambda 表達(dá)式.如果 lambda 表達(dá)式內(nèi)的代碼超過(guò)60-80個(gè)字符,最好定義成為常規(guī)的內(nèi)嵌函數(shù).

對(duì)于一般的操作諸如乘法,使用 operator 模塊內(nèi)置函數(shù)而非重新定義匿名函數(shù),例如使用 operator.mul而非lambda x,y: x * y

2.11 條件表達(dá)式

簡(jiǎn)單情況下可以使用.

2.11.1 定義

條件表達(dá)式(也稱(chēng)為三元運(yùn)算符)是一種更短替代 if 語(yǔ)句的機(jī)制.例如x = 1 if cond else 2

2.11.2 Pros

相對(duì)于 if 語(yǔ)句更短也更方便

2.11.3 Cons

比if語(yǔ)句可能更難讀懂,當(dāng)表達(dá)式很長(zhǎng)的時(shí)候條件部分可能很難定位.

2.11.4 建議

簡(jiǎn)單情況可以使用.每個(gè)部分(真值表達(dá)式,if表達(dá)式,else表達(dá)式)必須在一行內(nèi)完成.如果使用條件表達(dá)式很富的時(shí)候使用完整的if語(yǔ)句.

Yes:

one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value)
                  else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
    'yes, true, affirmative, confirmed, correct'
    if predicate(value)
    else 'no, false, negative, nay')

No:

bad_line_breaking = ('yes' if predicate(value) else
                     'no')portion_too_long = ('yes'
                    if some_long_module.some_long_predicate_function(
                        really_long_variable_name)
                    else 'no, false, negative, nay')

2.12 默認(rèn)參數(shù)值

大多數(shù)情況下都OK

2.12.1 定義

在函數(shù)參數(shù)列表的最后可以為變量設(shè)定值,例如 def foo(a, b=0):.如果 foo 在調(diào)用時(shí)只傳入一個(gè)參數(shù),那么b變量就被設(shè)定為0,如果調(diào)用時(shí)傳入兩個(gè)參數(shù),那么b就被賦予第二個(gè)參數(shù)值.

2.12.2 Pros

通常一個(gè)函數(shù)可能會(huì)有大量默認(rèn)值,但是很少會(huì)有需要修改這些默認(rèn)值的時(shí)候.默認(rèn)值就提供了一個(gè)很簡(jiǎn)單滿足上述情況的方式,而不需要為這些少見(jiàn)的情況重新定義很多函數(shù).因?yàn)?Python 不支持重載方法或函數(shù),默認(rèn)參數(shù)是一個(gè)很簡(jiǎn)單的方式來(lái)"假重載"行為.

2.12.3 Cons

默認(rèn)參數(shù)在模塊加載時(shí)就被復(fù)制.這在參數(shù)是可變對(duì)象(例如列表或字典)時(shí)引發(fā)問(wèn)題.如果函數(shù)修改了這些可變對(duì)象(例如向列表尾添加元素).默認(rèn)值就被改變了.

2.12.4 建議

使用時(shí)請(qǐng)注意以下警告----在函數(shù)或方法定義時(shí)不要將可變對(duì)象作為默認(rèn)值.

Yes:

def foo(a, b=None):
    if b is None:
        b = []
def foo(a, b: Optional[Sequence] = None):
    if b is None:
        b = []
def foo(a, b: Sequence = ()):  # Empty tuple OK since tuples are immutable 空元組是也不可變的
    ...

No:

def foo(a, b=[]):
    ...
def foo(a, b=time.time()):  # The time the module was loaded??? 模塊被加載的時(shí)間???
    ...
def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed... sys.argv還未被解析
    ...
def foo(a, b: Mapping = {}):  # Could still get passed to unchecked code 仍可傳入未檢查的代碼(此處翻譯可能有誤)
    ...

2.13 屬性

使用屬性可以通過(guò)簡(jiǎn)單而輕量級(jí)的訪問(wèn)器和設(shè)定器方法來(lái)訪問(wèn)或設(shè)定數(shù)據(jù).

2.13.1 定義

一種裝飾器調(diào)用來(lái)在計(jì)算比較輕量級(jí)時(shí)作為標(biāo)準(zhǔn)的屬性訪問(wèn)來(lái)獲取和設(shè)定一個(gè)屬性的方式

2.13.2 Pros

對(duì)于簡(jiǎn)單的屬性訪問(wèn),減少顯式的 get 和 set 方法能夠提升可讀性.允許惰性計(jì)算.被認(rèn)為是一種 Python 化的方式來(lái)維護(hù)類(lèi)接口.在表現(xiàn)上,當(dāng)直接對(duì)變量的訪問(wèn)更合理時(shí),允許屬性繞過(guò)所需的瑣碎的訪問(wèn)方法.

2.13.3 Cons

在 Python2中必須繼承于 object,可能會(huì)隱藏像是操作符重載之類(lèi)的副作用.對(duì)于子類(lèi)而言,屬性可能有些迷惑性.

2.13.4 建議

在通常會(huì)有簡(jiǎn)單而且輕量級(jí)的訪問(wèn)和設(shè)定方法的新代碼里使用屬性來(lái)訪問(wèn)或設(shè)定數(shù)據(jù).屬性在創(chuàng)建時(shí)被@property 裝飾,參加裝飾器

如果屬性本身未被重寫(xiě),帶有屬性的繼承可能不夠明晰,因而必須確保訪問(wèn)方法是被間接訪問(wèn)的,來(lái)確保子類(lèi)的方法重載是被屬性調(diào)用的(使用 Template Method DP,譯者:應(yīng)是模板方法設(shè)計(jì)模式).

Yes:

class Square(object):
    """A square with two properties: a writable area and a read-only perimeter.

    To use:
    >>> sq = Square(3)
    >>> sq.area
    9
    >>> sq.perimeter
    12
    >>> sq.area = 16
    >>> sq.side
    4
    >>> sq.perimeter
    16
    """

    def __init__(self, side):
        self.side = side

    @property
    def area(self):
        """Area of the square."""
        return self._get_area()

    @area.setter
    def area(self, area):
        return self._set_area(area)

    def _get_area(self):
        """Indirect accessor to calculate the 'area' property."""
        return self.side ** 2

    def _set_area(self, area):
        """Indirect setter to set the 'area' property."""
        self.side = math.sqrt(area)

    @property
    def perimeter(self):
        return self.side * 4

2.14 True/False表達(dá)式

只要可能,就使用隱式False的if語(yǔ)句

2.14.1 定義

在布爾環(huán)境下,Python 對(duì)某些值判定為False,一個(gè)快速的經(jīng)驗(yàn)規(guī)律是所有"空"值都被認(rèn)為是 False,所以0, None, [], {}, ''的布爾值都是 False

2.14.2 Pros

使用 Python 布爾類(lèi)型的條件語(yǔ)句可讀性更好而且更難出錯(cuò),大多數(shù)情況下,這種方式也更快.

2.14.3 Cons

對(duì)于 C/C++開(kāi)發(fā)者而言可能有些奇怪

建議

如果可能的話,使用隱式 False.例如使用 if foo:而非 if foo != []:下面列舉了一些你應(yīng)該牢記的警告:

  • 使用 if foo is None(或者 if foo is not None)來(lái)檢查None.例如在檢查一個(gè)默認(rèn)值是 None 的變量或者參數(shù)是否被賦予了其他值的時(shí)候,被賦予的其他值的布爾值可能為False.
  • 不要用==來(lái)和布爾值為 False 的變量比較,使用 if not x,如果需要區(qū)別 False 和 None,那么使用鏈?zhǔn)降谋磉_(dá)式如if not x and x is not None
  • 對(duì)于序列(如字符串,列表,元組),利用空序列為 False 的事實(shí),故而相應(yīng)地使用 if seq:和 if not seq:而非 if len(seq)或 if not len(seq):.
  • 在處理整數(shù)時(shí),隱式的 False 可能會(huì)引入更多風(fēng)險(xiǎn)(例如意外地將 None 和 0進(jìn)行了相同的處理)你可以用一個(gè)已知是整形(并且不是len()的結(jié)果)的值和整數(shù)0比較.

Yes:

if not users:
    print('no users')

if foo == 0:
    self.handle_zero()

if i % 10 == 0:
    self.handle_multiple_of_ten()

def f(x=None):
    if x is None:
        x = []

No:

if len(users) == 0:
    print('no users')

if foo is not None and not foo:
    self.handle_zero()

if not i % 10:
    self.handle_multiple_of_ten()

def f(x=None):
    x = x or []

2.15 棄用的語(yǔ)言特性

盡可能利用字符串方法而非 string 模塊.使用函數(shù)調(diào)用語(yǔ)法而非 apply.在函數(shù)參數(shù)本就是一個(gè)行內(nèi)匿名函數(shù)的時(shí)候,使用列表推導(dǎo)表達(dá)式和 for 循環(huán)而非 filter 和 map

2.15.1 定義

當(dāng)前 Python 版本提供了人們普遍更傾向的構(gòu)建方式.

2.15.2 建議

我們不使用任何不支持這些特性的 Python 版本,因而沒(méi)有理由不使用新方式.

Yes:

words = foo.split(':')

[x[1] for x in my_list if x[2] == 5]

map(math.sqrt, data)    # Ok. No inlined lambda expression. 可以,沒(méi)有行內(nèi)的lambda表達(dá)式

fn(*args, **kwargs)

No:

words = string.split(foo, ':')

map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))

apply(fn, args, kwargs)

2.16 詞法作用域

可以使用

2.16.1 定義

一個(gè)內(nèi)嵌 Python 函數(shù)可以引用在閉包命名空間內(nèi)定義的變量,但是不能對(duì)其復(fù)制.變量綁定是解析到使用詞法作用域的,即基于靜態(tài)程序文本.任何對(duì)塊內(nèi)命名的賦值都會(huì)讓 Python 將對(duì)于這個(gè)命名的引用都作為局部變量,即使在使用先于賦值的情況下也是.如果有全局聲明,這個(gè)命名就會(huì)被認(rèn)為是全局變量.

一個(gè)使用這個(gè)特性的例子是:

def get_adder(summand1):
    """Returns a function that adds numbers to a given number."""
    def adder(summand2):
        return summand1 + summand2

    return adder

2.16.2 Pros

經(jīng)??梢宰尨a更簡(jiǎn)明優(yōu)雅,尤其會(huì)讓有經(jīng)驗(yàn)的 Lisp 和 Scheme(以及 Haskell 和 ML 還有其他)的程序要很舒服.

2.16.3 Cons

可能會(huì)導(dǎo)致令人迷惑的 bug 例如這個(gè)基于PEP-0227的例子.

i = 4
def foo(x):
    def bar():
        print(i, end='')
    # ...
    # A bunch of code here
    # ...
    for i in x:  # Ah, i *is* local to foo, so this is what bar sees i對(duì)于foo來(lái)說(shuō)是局部變量,所以在這里就是bar函數(shù)所獲取的值
        print(i, end='')
    bar()

所以foo([1, 2, 3])會(huì)打印1 2 3 3而非1 2 3 4.

2.16.4 建議

可以使用

2.17 函數(shù)和方法裝飾器

在明顯有好處時(shí),謹(jǐn)慎明智的使用,避免@staticmethod,控制使用@classmethod

2.17.1 定義

函數(shù)和方法裝飾器(也就是@記號(hào)).一個(gè)常見(jiàn)的裝飾器是@property,用于將普通方法轉(zhuǎn)換成動(dòng)態(tài)計(jì)算屬性.然而裝飾器語(yǔ)法也允許用戶(hù)定義裝飾器,尤其對(duì)于一些函數(shù) my_decorator 如下:

class C(object):
    @my_decorator
    def method(self):
        # method body ...

是等效于

class C(object):
    def method(self):
        # method body ...
    method = my_decorator(method)

2.17.2 Pros

能夠優(yōu)雅的對(duì)方法進(jìn)行某種轉(zhuǎn)換,而該轉(zhuǎn)換可能減少一些重復(fù)代碼并保持不變性等等.

2.17.3 Cons

裝飾器可以對(duì)函數(shù)的參數(shù)和返回值任意操作,導(dǎo)致非常隱形的操作行為.此外,裝飾器在 import 的時(shí)候就被執(zhí)行,裝飾器代碼的實(shí)效可能非常難恢復(fù).

2.17.4 建議

在有明顯好處的地方謹(jǐn)慎地使用裝飾器.裝飾器應(yīng)該和函數(shù)遵守相同的 import 和命名指導(dǎo)規(guī)則.裝飾器的文檔應(yīng)該清晰地聲明該函數(shù)為裝飾器函數(shù).并且要為裝飾器函數(shù)編寫(xiě)單元測(cè)試.

避免裝飾器自身對(duì)外部的依賴(lài),(如不要依賴(lài)于文件,socket,數(shù)據(jù)庫(kù)連接等等),這是由于在裝飾器運(yùn)行的時(shí)候(在 import 時(shí),可能從 pydoc 或其他工具中)這些外部依賴(lài)可能不可用.一個(gè)被傳入有效參數(shù)并調(diào)用的裝飾器應(yīng)該(盡可能)保證在任何情況下都可用.

裝飾器是一種特殊的"頂級(jí)代碼",參見(jiàn)main

永遠(yuǎn)不要使用@staticmethod,除非不得不整合一個(gè) API 到一個(gè)已有的庫(kù),應(yīng)該寫(xiě)一個(gè)模塊等級(jí)的函數(shù).

只在寫(xiě)一個(gè)命名的構(gòu)造器或者一個(gè)類(lèi)特定的,修改必要的全局狀態(tài)(例如進(jìn)程緩存等)的流程時(shí)使用@classmethod.

2.18 線程

不要依賴(lài)于內(nèi)建類(lèi)型的原子性

盡管 Python 內(nèi)置數(shù)據(jù)類(lèi)型例如字典等似乎有原子性操作,仍有一些罕見(jiàn)情況下,他們是非原子的(比如,如果__hash__或者_(dá)_eq__被實(shí)現(xiàn)為Python方法),就不應(yīng)該依賴(lài)于這些類(lèi)型的原子性.也不應(yīng)該依賴(lài)于原子變量賦值(因?yàn)檫@依賴(lài)于字典)

優(yōu)先使用 Queue 模塊的 Queue 類(lèi)來(lái)作為線程之間通訊數(shù)據(jù)的方式.此外,要是用 threading 模塊和其 locking primitives(鎖原語(yǔ)).了解條件變量的合理用法以便于使用 threading.Condition 而非使用更低級(jí)的鎖.

2.19 過(guò)于強(qiáng)大的特性

盡量避免使用

2.19.1 定義

Python 是一種非常靈活的語(yǔ)言并且提供了很多新奇的特性,諸如定制元類(lèi),訪問(wèn)字節(jié)碼,動(dòng)態(tài)編譯,動(dòng)態(tài)繼承,對(duì)象父類(lèi)重定義,import hacks,反射(例如一些對(duì)于getattr()的應(yīng)用),系統(tǒng)內(nèi)置的修改等等.

2.19.2 Pros

這些是非常強(qiáng)大的語(yǔ)言特性,可以讓程序更緊湊

2.19.3 Cons

使用這些新特性是很誘人的.但是并不絕對(duì)必要,它們很難讀很難理解.也很難 debug 那些在底層使用了不常見(jiàn)的特性的代碼.對(duì)于原作者而言可能不是這樣,但是再次看代碼的時(shí)候,可能比更長(zhǎng)但是更直接的代碼要難.

2.19.4 定義

避免在代碼中使用這些特性.

內(nèi)部使用這些特性的標(biāo)準(zhǔn)庫(kù)和類(lèi)是可以使用的(例如 abc.ABCMeta,collections.namedtuple,和enum)

2.20 新版本Python: Python3 和從__future__import

Python3已經(jīng)可用了(譯者:目前 Python2已經(jīng)不受支持了),盡管不是每個(gè)項(xiàng)目都準(zhǔn)備好使用 Python3,所有的代碼應(yīng)該兼容 Python3并且在可能的情況下在 Python3的環(huán)境下測(cè)試.

2.20.1 定義

Python3是 Python的重大改變,盡管現(xiàn)有代碼通常是 Python2.7寫(xiě)成的,但可以做一些簡(jiǎn)單的事情來(lái)讓代碼更加明確地表達(dá)其意圖,從而可以讓代碼更好地在 Python3下運(yùn)行而不用調(diào)整.

2.20.2 Pros

在考慮 Python3編寫(xiě)的代碼更清晰明確,一旦所有依賴(lài)已就緒,就可以更容易在 Python3環(huán)境下運(yùn)行.

2.20.3 Cons

一些人會(huì)認(rèn)為默認(rèn)樣板有些丑,import實(shí)際不需要的特性到模塊中是不常見(jiàn)的.

2.20.4 建議

from future imports

鼓勵(lì)使用from __future__ import語(yǔ)句.所有新代碼都應(yīng)該包含下述代碼,而現(xiàn)有代碼應(yīng)該被更新以盡可能兼容:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

如果你不太熟悉這些,詳細(xì)閱讀這些:絕對(duì)import,新的/除法行為,和print函數(shù)

請(qǐng)勿省略或移除這些import,即使在模塊中他們沒(méi)有在使用,除非代碼只用于Python3.最好總是在所有的文檔中都有從future的import,來(lái)保證不會(huì)在有人使用在后續(xù)編輯時(shí)遺忘.

有其他的from __future__import語(yǔ)句,看喜好使用.我們的建議中不包含unicode_literals因?yàn)槠洳o(wú)明顯優(yōu)勢(shì),這是由于隱式默認(rèn)的編碼轉(zhuǎn)換導(dǎo)致其在Python2.7內(nèi)很多地方被引入了,必要時(shí),大多數(shù)代碼最好顯式的使用b''和u''btyes和unicode字符串表示.(譯者:這段翻譯可能不準(zhǔn)確)

The six, future, or past libraries

當(dāng)項(xiàng)目需要支持Python2和3時(shí),根據(jù)需求使用six,future和past.

2.21 帶有類(lèi)型注釋的代碼

可以根據(jù)PEP-484對(duì)Python3代碼進(jìn)行類(lèi)型注釋,并且在build時(shí)用類(lèi)型檢查工具例如pytype進(jìn)行類(lèi)型檢查.

類(lèi)型注釋可以在源碼中或stub pyi file中.只要可能,注釋就應(yīng)寫(xiě)在源代碼中.對(duì)于第三方或拓展模塊使用pyi文件.

2.21.1 定義

類(lèi)型注釋(也稱(chēng)為"類(lèi)型提示")是用于函數(shù)或方法參數(shù)和返回值的:

def func(a: int) -> List[int]:

你也可以聲明用一個(gè)單獨(dú)的注釋來(lái)聲明變量的類(lèi)型:

a = SomeFunc()  # type: SomeType

2.21.2 Pros

類(lèi)型注釋提升代碼的可讀性和可維護(hù)性,類(lèi)型檢查會(huì)將很多運(yùn)行錯(cuò)誤轉(zhuǎn)化為構(gòu)建錯(cuò)誤,也減少了使用過(guò)于強(qiáng)力特性的能力.

2.21.3 Cons

需要不斷更新類(lèi)型聲明,對(duì)于認(rèn)為有效的代碼可能會(huì)報(bào)類(lèi)型錯(cuò)誤,使用類(lèi)型檢查可能減少使用過(guò)于強(qiáng)力特性的能力.

2.21.4 建議

強(qiáng)烈鼓勵(lì)在更新代碼的時(shí)候進(jìn)行Python類(lèi)型分析.在對(duì)公共API進(jìn)行補(bǔ)充和修改時(shí),包括python類(lèi)型聲明并通過(guò)構(gòu)建系統(tǒng)中的pytype進(jìn)行檢查.對(duì)Python來(lái)說(shuō)靜態(tài)類(lèi)型檢查比較新,我們承認(rèn),一些意料外的副作用(例如錯(cuò)誤推斷的類(lèi)型)可能拒絕一些項(xiàng)目的使用.這種情況下,鼓勵(lì)作者適當(dāng)?shù)卦黾右粋€(gè)帶有TODO或到bug描述當(dāng)前不接搜的類(lèi)型注釋的鏈接到BUILD文件或者在代碼內(nèi).

3 Python代碼風(fēng)格規(guī)范

3.1 分號(hào)

不要在行尾加分號(hào),也不要用分號(hào)把兩行語(yǔ)句合并到一行

3.2 行長(zhǎng)度

最大行長(zhǎng)度是80個(gè)字符

超出80字符的明確例外:

  • 長(zhǎng)import
  • 注釋中的:URL,路徑,flags等
  • 不包含空格不方便分行的模塊級(jí)別的長(zhǎng)字符串常量
  • pylint的diable注釋使用(如# pylint: disable=invalid-name)

不要使用反斜杠連接,除非對(duì)于需要三層或以上的上下文管理器with語(yǔ)句

利用Python的implicit line joining inside parentheses, brackets and braces(隱式行連接方法--括號(hào)連接,包括(), [], {}).如果必要的話,也可在表達(dá)式外面額外添加一對(duì)括號(hào).

Yes:

foo_bar(self, width, height, color='black', design=None, x='foo',
emphasis=None, highlight=0)

if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):

當(dāng)字符串不能在一行內(nèi)完成時(shí),使用括號(hào)來(lái)隱式連接行:

x = ('This will build a very long long '
     'long long long long long long string')

在注釋內(nèi),如有必要,將長(zhǎng)URL放在其本行內(nèi):

Yes:

# See details at
# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html

No:

# See details at
# http://www.example.com/us/developer/documentation/api/content/\
# v2.0/csv_file_name_extension_full_specification.html

在定義一個(gè)表達(dá)式超過(guò)三行或更多的with語(yǔ)句時(shí),可以使用反斜杠來(lái)分行.對(duì)于兩行表達(dá)式,使用嵌套with語(yǔ)句:

Yes:

with very_long_first_expression_function() as spam, \
     very_long_second_expression_function() as beans, \
     third_thing() as eggs:
    place_order(eggs, beans, spam, beans)

with very_long_first_expression_function() as spam:
    with very_long_second_expression_function() as beans:
        place_order(beans, spam)

No:

with VeryLongFirstExpressionFunction() as spam, \
     VeryLongSecondExpressionFunction() as beans:
    PlaceOrder(eggs, beans, spam, beans)

注意上述例子中的縮進(jìn),具體參看縮進(jìn)

在其他一行超過(guò)80字符的情況下,而且yapf自動(dòng)格式工具也不能使分行符合要求時(shí),允許超過(guò)80字符限制.

3.3 括號(hào)

括號(hào)合理使用

盡管不必要,但是可以在元組外加括號(hào).再返回語(yǔ)句或者條件語(yǔ)句中不要使用括號(hào),除非是用于隱式的連接行或者指示元組.

Yes:

if foo:
    bar()
while x:
    x = bar()
if x and y:
    bar()
if not x:
    bar()
# For a 1 item tuple the ()s are more visually obvious than the comma.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...

No:

if (x):
    bar()
if not(x):
    bar()
return (foo)

3.4 縮進(jìn)

縮進(jìn)用4個(gè)空格

縮進(jìn)代碼段不要使用制表符,或者混用制表符和空格.如果連接多行,多行應(yīng)垂直對(duì)齊,或者再次4空格縮進(jìn)(這個(gè)情況下首行括號(hào)后應(yīng)該不包含代碼).

Yes:

# Aligned with opening delimiter
# 和opening delimiter對(duì)齊(譯者理解是分隔符的入口,例如三種括號(hào),字符串引號(hào)等)
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
meal = (spam,
        beans)

# Aligned with opening delimiter in a dictionary
foo = {
    long_dictionary_key: value1 +
                         value2,
    ...
}

# 4-space hanging indent; nothing on first line
# 縮進(jìn)4個(gè)空格,首行括號(hào)后無(wú)內(nèi)容
foo = long_function_name(
    var_one, var_two, var_three,
    var_four)
meal = (
    spam,
    beans)

# 4-space hanging indent in a dictionary
foo = {
    long_dictionary_key:
        long_dictionary_value,
    ...
}

No:

# Stuff on first line forbidden
# 首行不允許有內(nèi)容
foo = long_function_name(var_one, var_two,
    var_three, var_four)
meal = (spam,
    beans)

# 2-space hanging indent forbidden
foo = long_function_name(
  var_one, var_two, var_three,
  var_four)

# No hanging indent in a dictionary
foo = {
    long_dictionary_key:
    long_dictionary_value,
    ...
}

3.4.1 關(guān)于尾后逗號(hào)

關(guān)于在一序列元素中的尾號(hào)逗號(hào),只推薦在容器結(jié)束符號(hào)],)或者}和最后元素不在同一行時(shí)使用.尾后逗號(hào)的存在也被用作我們Python代碼自動(dòng)格式化工具yapf的提示,在,最后元素之后出現(xiàn)的時(shí)候來(lái)自動(dòng)調(diào)整容器元素到每行一個(gè)元素.

Yes:

golomb3 = [0, 1, 3]
golomb4 = [
    0,
    1,
    4,
    6,
]

No:

golomb4 = [
    0,
    1,
    4,
    6
]

3.5 空行

在頂級(jí)定義(函數(shù)或類(lèi))之間要間隔兩行.在方法定義之間以及class所在行與第一個(gè)方法之間要空一行,def行后無(wú)空行,在函數(shù)或方法內(nèi)你認(rèn)為合適地方可以使用單空行.

3.6 空格

遵守標(biāo)準(zhǔn)的空格和標(biāo)點(diǎn)排版規(guī)則.

括號(hào)(),[],{}內(nèi)部不要多余的空格.

Yes:

spam(ham[1], {eggs: 2}, [])

No:

spam( ham[ 1 ], { eggs: 2 }, [ ] )

逗號(hào)、分號(hào)、冒號(hào)前不要空格,但是在后面要加空格,除非是在行尾.

Yes:

if x == 4:
    print(x, y)
x, y = y, x

No:

if x == 4 :
    print(x , y)
x , y = y , x

在函數(shù)調(diào)用括號(hào)的前,索引切片括號(hào)前都不加空格.

Yes:

spam(1)
dict['key'] = list[index]

No:

spam (1)
dict ['key'] = list [index]

行尾不要加空格.

在賦值(=),比較(==,<,>,!=,<>,<=,>=,in,not in,is,is not),布爾符號(hào)(and,or,not)前后都加空格.視情況在算術(shù)運(yùn)算符(+,-,*,/,//,%,**,@),前后加空格

Yes:

x == 1

No:

x<1

在關(guān)鍵字名參數(shù)傳遞或定義默認(rèn)參數(shù)值的時(shí)候不要在=前后加空格,只有一個(gè)例外:當(dāng)類(lèi)型注釋存在時(shí)在定義默認(rèn)參數(shù)值時(shí)=前后加空格

Yes:

def complex(real, imag=0.0): return Magic(r=real, i=imag)
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)

No:

def complex(real, imag = 0.0): return Magic(r = real, i = imag)
def complex(real, imag: float=0.0): return Magic(r = real, i = imag)

不要用空格來(lái)做無(wú)必要的對(duì)齊,因?yàn)檫@會(huì)在維護(hù)時(shí)帶來(lái)不必要的負(fù)擔(dān)(對(duì)于:.#,=等等).

Yes:

foo = 1000  # comment
long_name = 2  # comment that should not be aligned
dictionary = {
    'foo': 1,
    'long_name': 2,
}

No:

foo       = 1000  # comment
long_name = 2     # comment that should not be aligned

dictionary = {
    'foo'      : 1,
    'long_name': 2,
}

3.7 Shebang

大部分.py文件不需要從#!行來(lái)開(kāi)始.根據(jù)PEP-394,程序的主文件應(yīng)該以#!/usr/bin/python2或#!/usr/bin/python3起始

這行被用于幫助內(nèi)核找到Python解釋器,但是在導(dǎo)入模塊時(shí)會(huì)被Python忽略/只在會(huì)被直接運(yùn)行的文件里有必要寫(xiě).

3.8 注釋和文檔字符串

確保使用正確的模塊,函數(shù),方法的文檔字符串和行內(nèi)注釋.

3.8.1 文檔字符串

Python使用文檔字符串來(lái)為代碼生成文檔.文檔字符串是包,模塊,類(lèi)或函數(shù)的首個(gè)語(yǔ)句.這些字符串能夠自動(dòng)被__doc__成員方法提取并且被pydoc使用.(嘗試在你的模塊上運(yùn)行pydoc來(lái)看看具體是什么).文檔字符串使用三重雙引號(hào)"""(根據(jù)PEP-257).文檔字符串應(yīng)該這樣組織:一行總結(jié)(或整個(gè)文檔字符串只有一行)并以句號(hào),問(wèn)好或感嘆號(hào)結(jié)尾.隨后是一行空行,隨后是文檔字符串,并與第一行的首個(gè)引號(hào)位置相對(duì)齊.更多具體格式規(guī)范如下.

3.8.2 模塊

每個(gè)文件都應(yīng)包含許可模板.選擇合適的許可模板用于項(xiàng)目(例如Apache 2.0,BSD,LGPL,GPL)

文檔應(yīng)該以文檔字符串開(kāi)頭,并描述模塊的內(nèi)容和使用方法.

"""A one line summary of the module or program, terminated by a period.

Leave one blank line.  The rest of this docstring should contain an
overall description of the module or program.  Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.

  Typical usage example:

  foo = ClassFoo()
  bar = foo.FunctionBar()
"""

3.8.3 函數(shù)和方法

在本節(jié),"函數(shù)"所指包括方法,函數(shù)或者生成器.

函數(shù)應(yīng)有文檔字符串,除非符合以下所有條件:

  • 外部不可見(jiàn)
  • 非常短
  • 簡(jiǎn)明

文檔字符串應(yīng)該包含足夠的信息以在無(wú)需閱讀函數(shù)代碼的情況下調(diào)用函數(shù).文檔字符串應(yīng)該是敘事體("""Fetches rows from a Bigtable.""")的而非命令式的("""Fetch rows from a Bigtable."""),除了@property(應(yīng)與attribute使用同樣的風(fēng)格).文檔字符串應(yīng)描述函數(shù)的調(diào)用語(yǔ)法和其意義,而非實(shí)現(xiàn).對(duì)比較有技巧的地方,在代碼中使用注釋更合適.

覆寫(xiě)了基類(lèi)的方法可有簡(jiǎn)單的文檔字符串向讀者指示被覆寫(xiě)方法的文檔字符串例如"""See base class.""".這是因?yàn)闆](méi)必要在很多地方重復(fù)已經(jīng)在基類(lèi)的文檔字符串中存在的文檔.不過(guò)如果覆寫(xiě)的方法行為實(shí)際上與被覆寫(xiě)方法不一致,或者需要提供細(xì)節(jié)(例如文檔中表明額外的副作用),覆寫(xiě)方法的文檔字符串至少要提供這些差別.

一個(gè)函數(shù)的不同方面應(yīng)該在特定對(duì)應(yīng)的分節(jié)里寫(xiě)入文檔,這些分節(jié)如下.每一節(jié)都由以冒號(hào)結(jié)尾的一行開(kāi)始, 每一節(jié)除了首行外,都應(yīng)該以2或4個(gè)空格縮進(jìn)并在整個(gè)文檔內(nèi)保持一致(譯者建議4個(gè)空格以維持整體一致).如果函數(shù)名和簽名足夠給出足夠信息并且能夠剛好被一行文檔字符串所描述,那么可以忽略這些節(jié).

Args:

列出每個(gè)參數(shù)的名字.名字后應(yīng)有為冒號(hào)和空格,后跟描述.如果描述太長(zhǎng)不能夠在80字符的單行內(nèi)完成.那么分行并縮進(jìn)2或4個(gè)空格且與全文檔一致(譯者同樣建議4個(gè)空格)

描述應(yīng)該包含參數(shù)所要求的類(lèi)型,如果代碼不包含類(lèi)型注釋的話.如果函數(shù)容許*foo(不定長(zhǎng)度參數(shù)列表)或**bar(任意關(guān)鍵字參數(shù)).那么就應(yīng)該在文檔字符串中列舉為*foo和**bar.

Returns:(或?qū)τ谏善魇荵ields:)

描述返回值的類(lèi)型和含義.如果函數(shù)至少返回None,這一小節(jié)不需要.如果文檔字符串以Returns或者Yields開(kāi)頭(例如"""Returns row from Bigtable as a tuple of strings.""")或首句足夠描述返回值的情況下這一節(jié)可忽略.

Raises:

列出所有和接口相關(guān)的異常.對(duì)于違反文檔要求而拋出的異常不應(yīng)列出.(因?yàn)檫@會(huì)矛盾地使得違反接口要求的行為成為接口的一部分)

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.

    Args:
        big_table: An open Bigtable Table instance.
        keys: A sequence of strings representing the key of each table row
            to fetch.
        other_silly_variable: Another optional variable, that has a much
            longer name than the other args, and which does nothing.

    Returns:
        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:
        IOError: An error occurred accessing the bigtable.Table object.
    """

3.8.4 類(lèi)

類(lèi)定義下一行應(yīng)為描述這個(gè)類(lèi)的文檔字符串.如果類(lèi)有公共屬性,應(yīng)該在文檔字符串中的Attributes節(jié)中注明,并且和函數(shù)的Args一節(jié)風(fēng)格統(tǒng)一.

class SampleClass(object):
    """Summary of class here.

    Longer class information....
    Longer class information....

    Attributes:
        likes_spam: A boolean indicating if we like SPAM or not.
        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."""

3.8.5 塊注釋和行注釋

最后要在代碼中注釋的地方是代碼技巧性的部分.如果你將要在下次code review中揭示代碼.應(yīng)該現(xiàn)在就添加注釋.在復(fù)雜操作開(kāi)始前,注釋幾行.對(duì)于不夠明晰的代碼在行尾注釋.

# We use a weighted dictionary search to find out where i is in
# the array.  We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.
if i & (i-1) == 0:  # True if i is 0 or a power of 2.

為了提升易讀性,行注釋?xiě)?yīng)該至少在代碼2個(gè)空格后,并以#后接至少1個(gè)空格開(kāi)始注釋部分.

另外,不要描述代碼,假定閱讀代碼的人比你更精通Python(他只是不知道你試圖做什么).

3.8.6 標(biāo)點(diǎn),拼寫(xiě)和語(yǔ)法

注意標(biāo)點(diǎn),拼寫(xiě)和語(yǔ)法,寫(xiě)得好的注釋要比寫(xiě)得差的好讀.

注釋?xiě)?yīng)當(dāng)是和敘事性文本一樣可讀,并具有合適的大小寫(xiě)和標(biāo)點(diǎn).在許多情況下,完整的句子要比破碎的句子更可讀.更簡(jiǎn)短的注釋如行尾的注釋有時(shí)會(huì)不太正式,但是應(yīng)該全篇保持風(fēng)格一致.

盡管被代碼審核人員指出在應(yīng)該使用分號(hào)的地方使用了逗號(hào)是很令人沮喪的,將源代碼維護(hù)在高度清楚可讀的程度是很重要的.合適的標(biāo)點(diǎn),拼寫(xiě)和語(yǔ)法能夠幫助達(dá)到這個(gè)目標(biāo).

3.9 類(lèi)

如果類(lèi)并非從其他基類(lèi)繼承而來(lái),那么就要明確是從object繼承而來(lái),即便內(nèi)嵌類(lèi)也是如此.

Yes:

class SampleClass(object):
    pass

class OuterClass(object):
    class InnerClass(object):
        pass

class ChildClass(ParentClass):
    """Explicitly inherits from another class already."""

No:

class SampleClass:
    pass

class OuterClass:
    class InnerClass:
        pass

從object類(lèi)繼承保證了屬性能夠在Python2正確運(yùn)行并且保護(hù)代碼在Python3下出現(xiàn)潛在的不兼容.這樣也定義了object包括__new__,__init__,__delattr__,__getattribute__,__setattr__,__hash__,__repr__,和__str__等默認(rèn)特殊方法的實(shí)現(xiàn).

3.10 字符串

使用format或%來(lái)格式化字符串,即使參數(shù)都是字符串對(duì)象,也要考慮使用+還是%及format.

Yes:

x = a + b
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}'.format(first, second)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
x = f'name: {name}; score: {n}'  # Python 3.6+

No:

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>'

避免使用+和+=操作符來(lái)在循環(huán)內(nèi)累加字符串,因?yàn)樽址遣豢勺儗?duì)象.這會(huì)造成不必要的臨時(shí)變量導(dǎo)致運(yùn)行時(shí)間以四次方增長(zhǎng)而非線性增長(zhǎng).應(yīng)將每個(gè)字符串都記入一個(gè)列表并使用''.join來(lái)將列表在循環(huán)結(jié)束后連接(或?qū)⒚總€(gè)子字符串寫(xiě)入io.BytesIO緩存)

Yes:

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)

No:

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>'

在同一個(gè)文件內(nèi),字符串引號(hào)要一致,選擇''或者""并且不要改變.對(duì)于需要避免\\轉(zhuǎn)義的時(shí)候,可以更改.

Yes:

Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')

No:

Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")

多行字符串多行字符串優(yōu)先使用"""而非''',當(dāng)且只當(dāng)對(duì)所有非文檔字符串的多行字符串都是用'''而且對(duì)正常字符串都使用'時(shí)才可使用三單引號(hào).docstring不論如何必須使用"""

多行字符串和其余代碼的縮進(jìn)方式不一致.如果需要避免在字符串中插入額外的空格,要么使用單行字符串連接或者帶有textwarp.dedent()的多行字符串來(lái)移除每行的起始空格.

No:

long_string = """This is pretty ugly.
Don't do this.
"""

Yes:

long_string = """This is fine if your use case can accept
    extraneous leading spaces."""

long_string = ("And this is fine if you can not accept\n" +
               "extraneous leading spaces.")

long_string = ("And this too is fine if you can not accept\n"
               "extraneous leading spaces.")

import textwrap

long_string = textwrap.dedent("""\
    This is also fine, because textwrap.dedent()
    will collapse common leading spaces in each line.""")

3.11 文件和sock

0 人點(diǎn)贊