0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Google Python代碼風(fēng)格指南

科技綠洲 ? 來源:Python數(shù)據(jù)科學(xué) ? 作者:Python數(shù)據(jù)科學(xué) ? 2023-11-03 10:20 ? 次閱讀

1 背景

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

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

許多團(tuán)隊(duì)使用yapf自動格式工具來避免格式爭議

2 Python語言規(guī)則

2.1 Lint

對代碼使用pylint

2.1.1Definition(以下都譯為定義)

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

2.1.2 Pros

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

2.1.3 Cons

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

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

b. 壓制一些警告

c. 優(yōu)化工具

2.1.4 Decision(以下都譯為建議)

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

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

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

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

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

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

可以通過下述方式來獲得pylint警告列表:

pylint --list-msgs

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

pylint --help-msg=C6409

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

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

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

2.2 Imports

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

2.2.1 定義

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

2.2.2 Pros

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

2.2.3 Cons

模塊名可能會有沖突,一些模塊名可能很長,比較不方便

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ù)模塊名yy過長不利于引用的時(shí)候)
  • import y as z (僅在非常通用的簡寫的時(shí)候使用例如import numpy as np

sound.effects.echo為例:

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

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

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

2.3 包

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

2.3.1 Pros

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

2.3.2 Cons

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

2.3.3 建議

所有的新代碼都要從完整包名來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).
# 在代碼中只用包名來調(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操作依賴于受到外部參數(shù)控制的sys.path
# 那么哪一個(gè)可能的jodie模塊是作者希望import的呢?
import jodie

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

2.4 異常

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

2.4.1 定義

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

2.4.2 Pros

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

2.4.3 Cons

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

2.4.4 建議

異常必定遵循特定條件:

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

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

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

2.5 全局變量

避免全局變量

2.5.1 定義

在模塊級別或者作為類屬性聲明的變量

2.5.2 Pros

有些時(shí)候有用

2.5.3 Cons

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

2.5.4 建議

避免全局變量

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

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

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

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

2.6.1 定義

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

2.6.2 Pros

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

2.6.3 Cons

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

2.6.4 建議

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

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

在簡單情況下是可用的

2.7.1 定義

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

2.7.2 Pros

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

2.7.3 Cons

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

2.7.4 建議

簡單情況下使用時(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)算符

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

2.8.1 定義

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

Pros

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

Cons

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

建議

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

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語句的時(shí)候生成一個(gè)值.在生成一個(gè)值之后,生成器函數(shù)的運(yùn)行被掛起直到需要下一個(gè)值.

2.9.2 Pros

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

2.9.3 Cons

2.9.4 建議

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

2.10 Lambda表達(dá)式

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

2.10.1 定義

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

2.10.2 Pros

方便

2.10.3 Cons

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

2.10.4 建議

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

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

2.11 條件表達(dá)式

簡單情況下可以使用.

2.11.1 定義

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

2.11.2 Pros

相對于if語句更短也更方便

2.11.3 Cons

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

2.11.4 建議

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

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ù)可能會有大量默認(rèn)值,但是很少會有需要修改這些默認(rèn)值的時(shí)候.默認(rèn)值就提供了一個(gè)很簡單滿足上述情況的方式,而不需要為這些少見的情況重新定義很多函數(shù).因?yàn)镻ython不支持重載方法或函數(shù),默認(rèn)參數(shù)是一個(gè)很簡單的方式來"假重載"行為.

2.12.3 Cons

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

2.12.4 建議

使用時(shí)請注意以下警告----在函數(shù)或方法定義時(shí)不要將可變對象作為默認(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 屬性

使用屬性可以通過簡單而輕量級的訪問器和設(shè)定器方法來訪問或設(shè)定數(shù)據(jù).

2.13.1 定義

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

2.13.2 Pros

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

2.13.3 Cons

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

2.13.4 建議

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

如果屬性本身未被重寫,帶有屬性的繼承可能不夠明晰,因而必須確保訪問方法是被間接訪問的,來確保子類的方法重載是被屬性調(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語句

2.14.1 定義

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

2.14.2 Pros

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

2.14.3 Cons

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

建議

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

  • 使用if foo is None(或者if foo is not None)來檢查None.例如在檢查一個(gè)默認(rèn)值是None的變量或者參數(shù)是否被賦予了其他值的時(shí)候,被賦予的其他值的布爾值可能為False.
  • 不要用==來和布爾值為False的變量比較,使用if not x,如果需要區(qū)別FalseNone,那么使用鏈?zhǔn)降谋磉_(dá)式如if not x and x is not None
  • 對于序列(如字符串,列表,元組),利用空序列為False的事實(shí),故而相應(yīng)地使用if seq:if not seq:而非if len(seq)if not len(seq):.
  • 在處理整數(shù)時(shí),隱式的False可能會引入更多風(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 棄用的語言特性

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

2.15.1 定義

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

2.15.2 建議

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

Yes:

words = foo.split(':')

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

map(math.sqrt, data)    # Ok. No inlined lambda expression. 可以,沒有行內(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)定義的變量,但是不能對其復(fù)制.變量綁定是解析到使用詞法作用域的,即基于靜態(tài)程序文本.任何對塊內(nèi)命名的賦值都會讓Python將對于這個(gè)命名的引用都作為局部變量,即使在使用先于賦值的情況下也是.如果有全局聲明,這個(gè)命名就會被認(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更簡明優(yōu)雅,尤其會讓有經(jīng)驗(yàn)的Lisp和Scheme(以及Haskell和ML還有其他)的程序要很舒服.

2.16.3 Cons

可能會導(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對于foo來說是局部變量,所以在這里就是bar函數(shù)所獲取的值
        print(i, end='')
    bar()

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

2.16.4 建議

可以使用

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

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

2.17.1 定義

函數(shù)和方法裝飾器(也就是@記號).一個(gè)常見的裝飾器是@property,用于將普通方法轉(zhuǎn)換成動態(tài)計(jì)算屬性.然而裝飾器語法也允許用戶定義裝飾器,尤其對于一些函數(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)雅的對方法進(jìn)行某種轉(zhuǎn)換,而該轉(zhuǎn)換可能減少一些重復(fù)代碼并保持不變性等等.

2.17.3 Cons

裝飾器可以對函數(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ù)編寫單元測試.

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

裝飾器是一種特殊的"頂級代碼",參見main

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

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

2.18 線程

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

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

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

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

盡量避免使用

2.19.1 定義

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

2.19.2 Pros

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

2.19.3 Cons

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

2.19.4 定義

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

內(nèi)部使用這些特性的標(biāo)準(zhǔn)庫和類是可以使用的(例如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)境下測試.

2.20.1 定義

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

2.20.2 Pros

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

2.20.3 Cons

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

2.20.4 建議

from future imports

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

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

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

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

有其他的from __future__import語句,看喜好使用.我們的建議中不包含unicode_literals因?yàn)槠洳o明顯優(yōu)勢,這是由于隱式默認(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 帶有類型注釋的代碼

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

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

2.21.1 定義

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

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

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

a = SomeFunc()  # type: SomeType

2.21.2 Pros

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

2.21.3 Cons

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

2.21.4 建議

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

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

3.1 分號

不要在行尾加分號,也不要用分號把兩行語句合并到一行

3.2 行長度

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

超出80字符的明確例外:

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

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

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

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í),使用括號來隱式連接行:

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

在注釋內(nèi),如有必要,將長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á)式超過三行或更多的with語句時(shí),可以使用反斜杠來分行.對于兩行表達(dá)式,使用嵌套with語句:

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)

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

3.3 括號

括號合理使用

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

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)垂直對齊,或者再次4空格縮進(jìn)(這個(gè)情況下首行括號后應(yīng)該不包含代碼).

Yes:

# Aligned with opening delimiter
# 和opening delimiter對齊(譯者理解是分隔符的入口,例如三種括號,字符串引號等)
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è)空格,首行括號后無內(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)于尾后逗號

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

Yes:

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

No:

golomb4 = [
    0,
    1,
    4,
    6
]

3.5 空行

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

3.6 空格

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

括號(),[],{}內(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

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

Yes:

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

No:

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

行尾不要加空格.

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

Yes:

x == 1

No:

x< 1

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

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

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文件不需要從#!行來開始.根據(jù)PEP-394,程序的主文件應(yīng)該以#!/usr/bin/python2#!/usr/bin/python3起始

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

3.8 注釋和文檔字符串

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

3.8.1 文檔字符串

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

3.8.2 模塊

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

文檔應(yīng)該以文檔字符串開頭,并描述模塊的內(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)有文檔字符串,除非符合以下所有條件:

  • 外部不可見
  • 非常短
  • 簡明

文檔字符串應(yīng)該包含足夠的信息以在無需閱讀函數(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)用語法和其意義,而非實(shí)現(xiàn).對比較有技巧的地方,在代碼中使用注釋更合適.

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

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

Args:

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

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

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

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

Raises:

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

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 類

類定義下一行應(yīng)為描述這個(gè)類的文檔字符串.如果類有公共屬性,應(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ù)雜操作開始前,注釋幾行.對于不夠明晰的代碼在行尾注釋.

# 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.

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

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

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

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

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

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

3.9 類

如果類并非從其他基類繼承而來,那么就要明確是從object繼承而來,即便內(nè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類繼承保證了屬性能夠在Python2正確運(yùn)行并且保護(hù)代碼在Python3下出現(xiàn)潛在的不兼容.這樣也定義了object包括__new__,__init__,__delattr__,__getattribute__,__setattr__,__hash__,__repr__,和__str__等默認(rèn)特殊方法的實(shí)現(xiàn).

3.10 字符串

使用format%來格式化字符串,即使參數(shù)都是字符串對象,也要考慮使用+還是%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 >'

避免使用++=操作符來在循環(huán)內(nèi)累加字符串,因?yàn)樽址遣豢勺儗ο?這會造成不必要的臨時(shí)變量導(dǎo)致運(yùn)行時(shí)間以四次方增長而非線性增長.應(yīng)將每個(gè)字符串都記入一個(gè)列表并使用''.join來將列表在循環(huán)結(jié)束后連接(或?qū)⒚總€(gè)子字符串寫入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),字符串引號要一致,選擇''或者""并且不要改變.對于需要避免轉(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)對所有非文檔字符串的多行字符串都是用'''而且對正常字符串都使用'時(shí)才可使用三單引號.docstring不論如何必須使用"""

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

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 acceptn" +
               "extraneous leading spaces.")

long_string = ("And this too is fine if you can not acceptn"
               "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 文件和socket

當(dāng)使用結(jié)束后顯式地關(guān)閉文件或socket.

不必要地打開文件,socket或其他類似文件的對象有很多弊端:

  • 他們可能會消耗有限的系統(tǒng)資源,例如文件描述符.如果在使用沒有即使歸還系統(tǒng),處理很多這樣對象的代碼可能會浪費(fèi)掉很多不應(yīng)浪費(fèi)的資源.
  • 保持一個(gè)文件可能會阻止其他操作諸如移動或刪除.
  • 被程序共享的文件和socket可能會無意中在邏輯上已被關(guān)閉的情況下仍被讀寫.如果實(shí)際上已經(jīng)關(guān)閉,試圖讀寫的操作會拋出異常,這樣就可以立即發(fā)現(xiàn)問題.

此外,當(dāng)文件或socket在文件對象被銷毀的同時(shí)被自動關(guān)閉的時(shí)候,是不可能將文件的生命周期和文件狀態(tài)綁定的:

  • 不能保證何時(shí)會真正將文件對象銷毀.不同的Python解釋器使用的內(nèi)存管理技術(shù)不同,例如延時(shí)垃圾處理可能會讓對象的生命周期被無限期延長.
  • 可能導(dǎo)致意料之外地對文件對象的引用,例如在全局變量或者異?;厮葜?可能會讓文件對象比預(yù)計(jì)的生命周期更長.

推薦使用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.python.org/")) as front_page:
    for line in front_page:
        print(line)

3.12 TODO注釋

對于下述情況使用TODO注釋:臨時(shí)的,短期的解決方案或者足夠好但是不完美的解決方案.

TODO注釋以全部大寫的字符串TODO開頭,并帶有寫入括號內(nèi)的姓名,email地址,或其他可以標(biāo)識負(fù)責(zé)人或者包含關(guān)于問題最佳描述的issue.隨后是這里做什么的說明.

有統(tǒng)一風(fēng)格的TODO的目的是為了方便搜索并了解如何獲取更多相關(guān)細(xì)節(jié).TODO并不是保證被提及者會修復(fù)問題.因此在創(chuàng)建TODO注釋的時(shí)候,基本上都是給出你的名字.

# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

如果TODO注釋形式為"未來某個(gè)時(shí)間點(diǎn)會做什么事"的格式,確保要么給出一個(gè)非常具體的時(shí)間點(diǎn)(例如"將于2009年11月前修復(fù)")或者給出一個(gè)非常具體的事件(例如"當(dāng)所有客戶端都能夠處理XML響應(yīng)時(shí)就移除此代碼").

3.13 import格式

imports應(yīng)該在不同行.例如:

Yes:

import os
import sys

No:

import os, sys

import應(yīng)集中放在文件頂部,在模塊注釋和docstring后面,模塊globals和常量前面.應(yīng)按照從最通用到最不通用的順序排列分組:

  1. Python未來版本import語句,例如:

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

    更多信息參看上文

  2. Python標(biāo)準(zhǔn)基礎(chǔ)庫import,例如:

    import sys
    
  3. 第三方庫或包的import,例如:

    import tensorflow as tf
    
  4. 代碼庫內(nèi)子包import,例如:

    from otherproject.ai import mind
    
  5. 此條已棄用:和當(dāng)前文件是同一頂級子包專用的import,例如:

    from myproject.backend.hgwells import time_machine
    

    在舊版本的谷歌Python代碼風(fēng)格指南中實(shí)際上是這樣做的.但是現(xiàn)在不再需要了.**新的代碼風(fēng)格不再受此困擾.**簡單的將專用的子包import和其他子包import同一對待即可.

在每個(gè)組內(nèi)按照每個(gè)模塊的完整包路徑的字典序忽略大小寫排序.可以根據(jù)情況在每個(gè)節(jié)質(zhì)檢增加空行.

import collectionsimport queueimport sys

from absl import appfrom absl import flagsimport bs4import cryptographyimport tensorflow as tf

from book.genres import scififrom myproject.backend.hgwells import time_machinefrom myproject.backend.state_machine import main_loopfrom otherproject.ai import bodyfrom otherproject.ai import mindfrom otherproject.ai import soul

# Older style code may have these imports down here instead:
# 舊版本代碼風(fēng)格可能會采用下述import方式
# from myproject.backend.hgwells import time_machine
# from myproject.backend.state_machine import main_loop

3.14 語句

每行只有一條語句.

不過如果測試語句和結(jié)果能夠在一行內(nèi)放下,就可以放在一行內(nèi).但是不允許將try/except語句和對應(yīng)內(nèi)容放于一行,因?yàn)?code>try或者except都不能在一行內(nèi)完成.對于沒有else的if語句可以將if和對應(yīng)內(nèi)容合并到一行.

Yes:

if foo: bar(foo)

No:

if foo: bar(foo)
else:   baz(foo)

try:               bar(foo)
except ValueError: baz(foo)

try:
    bar(foo)
except ValueError: baz(foo)

3.15 訪問

對于瑣碎又不太重要的訪問函數(shù),應(yīng)用公共變量來替代訪問函數(shù),以避免額外的程序調(diào)用消耗,當(dāng)添加了更多函數(shù)功能時(shí),使用property來保持連續(xù)性

此外,如果訪問過于復(fù)雜,或者訪問變量的消耗過大,應(yīng)該使用諸如get_foo()set_foo()之類的函數(shù)式訪問(參考命名指南).如果過去的訪問方式是通過屬性,新訪問函數(shù)不要綁定到property上,這樣使用property的舊方式就會失效,使用者就會知道函數(shù)有變化.

3.16 命名

module_name,package_name,ClassName,method_name,ExceptionName,function_name,GLOBAL_CONSTANT_NAME,global_var_name,instance_var_name,function_parameter_name,local_var_name.

命名函數(shù)名,變量名,文件名應(yīng)該是描述性的,避免縮寫,尤其避免模糊或?qū)ψx者不熟悉的縮寫.并且不要通過刪減單詞內(nèi)的字母來縮短.

使用.py作為文件拓展名,不要使用橫線.

3.16.1 要避免的名字:

  • 單字符名字,除非是計(jì)數(shù)或迭代元素,e可以作為Exception捕獲識別名來使用..
  • -橫線,不應(yīng)出現(xiàn)在任何包名或模塊名內(nèi)
  • __double_leading_and_trailing_underscore__首尾都雙下劃線的名字,這種名字是python的內(nèi)置保留名字

3.16.4 命名約定

  • internal表示僅模塊內(nèi)可用、或者類內(nèi)保護(hù)的或者私有的
  • 單下劃線(_)開頭表示是被保護(hù)的(from module import *不會import).雙下劃線(__也就是"dunder")開頭的實(shí)例變量或者方法表示類內(nèi)私有(使用命名修飾).我們不鼓勵使用,因?yàn)檫@會對可讀性和可測試性有削弱二期并非真正的私有.
  • 相關(guān)的類和頂級函數(shù)放在同一個(gè)模塊內(nèi),不必像是Java一樣要一個(gè)類放在一個(gè)模塊里.
  • 對類名使用大寫字母(如CapWords)開頭的單詞,命名,模塊名應(yīng)該使用小寫加下劃線的方式.盡管有一些舊的模塊命名方式是大寫字母的(如CapWords.py),現(xiàn)在不鼓勵這樣做了,因?yàn)樵谀K剛好是從某個(gè)類命名出發(fā)的時(shí)候可能會令人迷惑(例如是選擇import StringIO還是from StringIO import StringIO?)
  • unittest方法中可能是test開頭來分割名字的組成部分,即使這些組成部分是使用大寫字母駝峰式的.這種方式是可以的:test_例如testPop_EmptyStack,對于命名測試方法沒有明確的正確方法.

3.16.3 文件名

文件拓展名必須為.py,不可以包含-.這保證了能夠被正常import和單元測試.如果希望一個(gè)可執(zhí)行文件不需要拓展名就可以被調(diào)用,那么建立一個(gè)軟連接或者一個(gè)簡單的bash打包腳本包括exec "$0.py" "$@".

3.16.4 Guido的指導(dǎo)建議

類型公共內(nèi)部
lower_with_under
模塊lower_with_under_lower_with_under
CapWords_CapWords
異常CapWords
函數(shù)lower_with_under()_lower_with_under()
全局/類常量CAPS_WITH_UNDER_CAPS_WITH_UNDER
全局/類變量lower_with_under_lower_with_under
實(shí)例變量lower_with_under_lower_with_under(受保護(hù))
方法名lower_with_under()_lower_with_under()(受保護(hù))
函數(shù)/方法參數(shù)lower_with_under
局部變量lower_with_under

盡管Python支持通過雙下劃線__(即"dunder")來私有化.不鼓勵這樣做.優(yōu)先使用單下劃線.單下劃線更易于打出來、易讀、易于小的單元測試調(diào)用.Lint的警告關(guān)注受保護(hù)成員的無效訪問.

3.17 Main

即便是一個(gè)用做腳本的py文件也應(yīng)該是可以被import的,而只用于import時(shí),也不應(yīng)有執(zhí)行了主函數(shù)的副作用.主函數(shù)的功能應(yīng)該被放在main()里.

在Python中,pydoc和單元測試要求模塊是可import的.所以代碼在主程序執(zhí)行前應(yīng)進(jìn)行if __name__ == '__main__':檢查,以防止模塊在import時(shí)被執(zhí)行.

def main():
    ...

if __name__ == '__main__':
    main()

所有頂級代碼在模塊被import時(shí)執(zhí)行.因而要小心不要調(diào)用函數(shù),創(chuàng)建對象或者執(zhí)行其他在執(zhí)行pydoc時(shí)不應(yīng)該被執(zhí)行的操作.

3.18 函數(shù)長度

優(yōu)先寫小而專一的函數(shù).

長函數(shù)有時(shí)候是合適的,故而函數(shù)長度沒有固定的限制.但是超過40行的時(shí)候就要考慮是否要在不影響程序結(jié)構(gòu)的前提下分解函數(shù).

盡管長函數(shù)現(xiàn)在運(yùn)行的很好,但是在之后的時(shí)間里其他人修改函數(shù)并增加新功能的時(shí)候可能會引入新的難以發(fā)現(xiàn)的bug,保持函數(shù)的簡短,這樣有利于其他人讀懂和修改代碼.

在處理一些代碼時(shí),可能會發(fā)現(xiàn)有些函數(shù)長而且復(fù)雜.不要畏懼調(diào)整現(xiàn)有代碼,如果處理這個(gè)函數(shù)非常困難,如難以對報(bào)錯debug或者希望在幾個(gè)不同的上下文中使用它,那么請將函數(shù)拆解成若干個(gè)更小更可控的片段.

3.19 類型注釋

3.19.1 基本規(guī)則

  • 熟悉PEP-484
  • 在方法中,只在必要時(shí)給self或者cls增加合適的類型信息.例如@classmethod def create(cls: Type[T]) -> T: return cls()
  • 如果其他變量或返回類型不定,使用Any
  • 不需要注釋每個(gè)函數(shù)
    • 至少需要注明公共接口
    • 使用類型檢查來在安全性和聲明清晰性以及靈活性之間平衡
    • 標(biāo)注容易因類型相關(guān)而拋出異常的代碼(previous bugs or complexity,此處譯者認(rèn)為是與上一條一致,平衡安全性和復(fù)雜性)
    • 標(biāo)注難理解的代碼
    • 標(biāo)注類型穩(wěn)定的代碼,成熟穩(wěn)定的代碼可以都進(jìn)行標(biāo)注而不會影響其靈活性

3.19.2 分行

遵循現(xiàn)有的縮進(jìn)規(guī)范

標(biāo)注類型后,函數(shù)簽名多數(shù)都要是"每行一個(gè)參數(shù)".

def my_method(self,
              first_var: int,
              second_var: Foo,
              third_var: Optional[Bar]) - > int:
  ...

優(yōu)先在變量之間換行,而非其他地方(如變量名和類型注釋之間).如果都能放在一行內(nèi),就放在一行.

def my_method(self, first_var: int) - > int:
  ...

如果函數(shù)名,一直到最后的參數(shù)以及返回類型注釋放在一行過長,那么分行并縮進(jìn)4個(gè)空格.

def my_method(
    self, first_var: int) - > Tuple[MyLongType1, MyLongType1]:
  ...

當(dāng)返回值類型不能和最后一個(gè)參數(shù)放入同一行,比較好的處理方式是將參數(shù)分行并縮進(jìn)4個(gè)空格,右括號和返回值類型換行并和def對齊.

def my_method(
    self, other_arg: Optional[MyLongType]
) - > Dict[OtherLongType, MyLongType]:
  ...

pylint允許您將右括號移動到新行并與左括號對齊,但這不太容易理解.

No:

def my_method(self,
              other_arg: Optional[MyLongType]
             ) - > Dict[OtherLongType, MyLongType]:
  ...

就像上面的例子一樣,盡量不要分割類型注釋,不過有時(shí)類型注釋太長無法放入一行,(那就盡量讓子注釋不要被分割).

def my_method(
    self,
    first_var: Tuple[List[MyLongType1],
                     List[MyLongType2]],
    second_var: List[Dict[
        MyLongType3, MyLongType4]]) - > None:
  ...

如果某個(gè)命名和類型太長了,考慮使用別名.如果沒有其他解決方案,在冒號后分行縮進(jìn)4個(gè)空格.

Yes:

def my_function(
    long_variable_name:
        long_module_name.LongTypeName,
) - > None:
  ...

No:

def my_function(
    long_variable_name: long_module_name.
        LongTypeName,) - > None:
  ...

3.19.3 前置聲明

如果需要同一模塊內(nèi)還未定義的類名,例如需要類聲明內(nèi)部的類,或者需要在后續(xù)代碼中定義的類,那么使用類名的字符串來代替.

class MyClass(object):

  def __init__(self,
               stack: List["MyClass"]) - > None:

3.19.4 默認(rèn)值

參考PEP-008,只有在同時(shí)需要類型注釋和默認(rèn)值的時(shí)候在=前后都加空格

Yes:

def func(a: int = 0) - > int:
  ...

No:

def func(a:int=0) - > int:
  ...

3.19.5 NoneType

在Python系統(tǒng)中NoneType是一等類型,為了方便輸入,NoneNoneType的別名.如果一個(gè)參數(shù)可以是None,那么就需要聲明!可以使用Union,但如果只有一個(gè)其他類型,那么使用Optional.

顯式地使用Optional而非隱式地.PEP 484的早期版本容許a: Text = None被解釋為a: Optional[Text] = None.但現(xiàn)在已經(jīng)不推薦這樣使用了.

Yes:

def func(a: Optional[Text], b: Optional[Text] = None) - > Text:
  ...
def multiple_nullable_union(a: Union[None, Text, int]) - > Text
  ...

No:

def nullable_union(a: Union[None, Text]) - > Text:
  ...
def implicit_optional(a: Text = None) - > Text:
  ...

3.19.6 類型別名

可以對復(fù)雜類型聲明別名,別名的名稱應(yīng)為CapWorded,如果只用于當(dāng)前模塊,應(yīng)加下劃線私有化.

例如,如果帶有模塊名的類型名過長:

_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[Text, List[Tuple[int, int]]]

其他示例是復(fù)雜的嵌套類型和一個(gè)函數(shù)的多個(gè)返回變量(作為元組).

3.19.7 忽略類型檢查

可以通過增加特殊行注釋# type: ignore來禁止類型檢查.

pytype對于明確的報(bào)錯有關(guān)閉選項(xiàng)(類似于lint):

# pytype: disable=attribute-error

3.19.8 對變量注釋類型

對變量標(biāo)注類型如果內(nèi)部變量很難或者不可能指向,可以使用下述方式:

類型注釋 :

在行尾增加以# type開頭的注釋

a = SomeUndecoratedFunction()  # type: Foo

注釋綁定 :

在變量名和賦值之間用冒號和類型注明,和函數(shù)參數(shù)一致.

a: Foo = SomeUndecoratedFunction()

3.19.9 元組和列表

不像是列表只能包含單一類型,元組可以既只有一種重復(fù)類型或者一組不同類型的元素,后者常用于函數(shù)返回.

a = [1, 2, 3]  # type: List[int]
b = (1, 2, 3)  # type: Tuple[int, ...]
c = (1, "2", 3.5)  # type: Tuple[int, Text, float]

3.19.10 TypeVars

Python是有泛型的,工廠函數(shù)TypeVar是通用的使用方式.

例子:

from typing import List, TypeVar
T = TypeVar("T")
...
def next(l: List[T]) - > T:
  return l.pop()

TypeVar可以約束類型:

AddableType = TypeVar("AddableType", int, float, Text)
def add(a: AddableType, b: AddableType) - > AddableType:
    return a + b

typing模塊預(yù)定義好的類型變量是AnyStr,用于針對字符串可以是bytes也可為unicode并且保持一致的多個(gè)類型注釋.

from typing import AnyStr
def check_length(x: AnyStr) - > AnyStr:
  if len(x) <= 42:
    return x
  raise ValueError()

3.19.11 字符串類型

注釋字符串的合適類型是基于Python版本的.

對于只有Python3的代碼,使用str,Text可以用但是在選擇上保持一致.

對于Python2兼容的代碼,用Text,在一些很罕見的情況下,str可能可用.當(dāng)在不同Python版本之間返回值類型不同的時(shí)候通常是為了照顧兼容性.避免使用unicode,因?yàn)镻ython3中不存在.

No:

def py2_code(x: str) - > unicode:
  ...

對于處理二進(jìn)制數(shù)據(jù)的代碼,請使用bytes.

Yes:

def deals_with_binary_data(x: bytes) - > bytes:
  ...

對于Python2兼容,處理文本數(shù)據(jù)(Python中strunicode,Python3中str)的代碼,使用Text.對于只有Python3的代碼,優(yōu)先使用str.

from typing import Text
...
def py2_compatible(x: Text) - > Text:
  ...
def py3_only(x: str) - > str:
  ...

如果既可以是byte也可以是文本,那么使用Union和合適的文本類型.

from typing import Text, Union
...
def py2_compatible(x: Union[bytes, Text]) - > Union[bytes, Text]:
  ...
def py3_only(x: Union[bytes, str]) - > Union[bytes, str]:
  ...

如果一個(gè)函數(shù)中所有的字符串類型始終一致,例如前文例子中返回值類型和參數(shù)類型是一致的,那么使用AnyStr

像這樣寫能夠簡化代碼向Python3的遷移過程.

3.19.12 typing的import

對于從typing模塊import的類,要import類本身.明確的允許在一行內(nèi)從typing模塊import多個(gè)特定的類,如

from typing import Any, Dict, Optional

這種從typing模塊import的方式會向命名空間內(nèi)增加額外項(xiàng),typing中的任何命名都應(yīng)該和關(guān)鍵字同等對待并且不在你的Python代碼中定義,typed or not(譯者推測文無論是否引入).如果和已有的命名沖突,使用import x as y來import.

from typing import Any as AnyType

3.19.13 條件import

只在運(yùn)行時(shí)一定要避免進(jìn)行類型檢查的情況下使用條件import.不鼓勵使用這種模式.鼓勵使用其他替代方式諸如重構(gòu)代碼以容許頂級import.

只用于類型注釋的import可以被歸于if TYPE_CHECKING:代碼塊中.

  • 條件import的類型應(yīng)被視為字符串引用,以和Python3.6兼容(在Python3.6中,注釋表達(dá)式實(shí)際上被賦值的).
  • 只有單獨(dú)用于類型注釋的實(shí)例才能在這里定義,包括了別名.否則將會報(bào)運(yùn)行錯誤因?yàn)樵谶\(yùn)行時(shí)這些模塊不會被引用.
  • 代碼塊應(yīng)該緊跟在正常import后面.
  • 在類型import后不應(yīng)有空行
  • 按照正常import順序?qū)@一塊代碼進(jìn)行排序
import typing
if typing.TYPE_CHECKING:
    import sketch
def f(x: "sketch.Sketch"): ...

3.19.14 循環(huán)依賴

由于類型檢查引發(fā)的循環(huán)依賴是一種code smell(代碼異味),這樣的代碼應(yīng)當(dāng)被重構(gòu).盡管技術(shù)上是可以保留循環(huán)引用的.build system(系統(tǒng))不允許這樣做因?yàn)槊總€(gè)模塊都要依賴于其他模塊.

將造成循環(huán)依賴的模塊替換為Any并賦予一個(gè)有意義的別名并使用從這個(gè)模塊導(dǎo)入的真實(shí)類名(因?yàn)槿魏?code>Any的屬性都是Any).別名的定義用和最后一行import用一行空行分隔.

from typing import Any

some_mod = Any  # some_mod.py imports this module.
...

def my_method(self, var: some_mod.SomeType) - > None:
  ...

3.19.15 泛型

當(dāng)注釋的時(shí)候,優(yōu)先泛型類型專有類型參數(shù),否則泛型的參數(shù)會被認(rèn)為是Any.

def get_names(employee_ids: List[int]) - > Dict[int, Any]:
  ...
# These are both interpreted as get_names(employee_ids: List[Any]) - > Dict[Any, Any]
def get_names(employee_ids: list) - > Dict:
  ...

def get_names(employee_ids: List) - > Dict:
  ...

如果泛型最佳的參數(shù)類型是Any也將其顯式地表示出來.但是在很多情況下TypeVar可能更合適.

def get_names(employee_ids: List[Any]) - > Dict[Any, Text]:
"""Returns a mapping from employee ID to employee name for given IDs."""
T = TypeVar('T')
def get_names(employee_ids: List[T]) - > Dict[T, Text]:
"""Returns a mapping from employee ID to employee name for given IDs."""

4 最后的話

如果你在編輯代碼,花幾分鐘看看現(xiàn)有代碼然后決定好要使用哪種風(fēng)格.如果現(xiàn)有代碼在所有算術(shù)運(yùn)算符兩側(cè)都加了空格,那么你也應(yīng)該如此.如果現(xiàn)有的注釋用井號組成了包圍框,那么你的注釋也應(yīng)如此.

有代碼風(fēng)格指南的目的是有一個(gè)編程的共識,這樣人們能夠集中在內(nèi)容而非形式上.我們將通用的代碼風(fēng)格指南公布于此這樣人們就能了解這個(gè)共識(譯者:有巴別塔的意味.)但是各自的代碼風(fēng)格也很重要.如果你添加的代碼與原有代碼看起來完全不一致,就會打亂讀者的閱讀節(jié)奏,避免這樣。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Google
    +關(guān)注

    關(guān)注

    5

    文章

    1748

    瀏覽量

    57187
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3521

    瀏覽量

    93268
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4671

    瀏覽量

    67765
  • python
    +關(guān)注

    關(guān)注

    53

    文章

    4753

    瀏覽量

    84077
收藏 人收藏

    評論

    相關(guān)推薦

    MATLAB 編程風(fēng)格指南

    thebeginning.”(良好的寫作規(guī)范的程序比糟糕的寫作規(guī)范的要好,因?yàn)樗麄兙哂休^少的錯誤、易于調(diào)試與修改,因此,從一開始就考慮風(fēng)格是很重要的)。本指南列舉的MATLAB 代碼編寫的建議在
    發(fā)表于 09-22 16:19

    Google C++編程指南

    Google C++編程指南目標(biāo):增強(qiáng)代碼一致性,創(chuàng)建通用的、必需的習(xí)慣用語和模式可以使代碼更加容易理解C++是一門包含大量高級特性的巨型語言,某些情況下,我們會限制甚至禁止使用某些特
    發(fā)表于 11-29 09:15

    Linux內(nèi)核編碼風(fēng)格(編程代碼風(fēng)格推薦)

    這是翻譯版本,英文原版是linux源碼Documentation文件夾下的CodingStyle一個(gè)良好風(fēng)格的程序看起來直觀、美觀,便于閱讀,還能有助于對程序的理解,特別在代碼量比較大情況下更顯
    發(fā)表于 08-24 09:45

    關(guān)于CubeIde自動檢查STM32CubeIde代碼風(fēng)格檢查器的問題求解

    STM32CubeIde /eclipse 自帶一個(gè)代碼風(fēng)格檢查器。我剛剛運(yùn)行它。默認(rèn)設(shè)置不涵蓋 ST 的代碼風(fēng)格。是否有可能獲得一個(gè)配置文件,以便 CubeIde 自動檢查 STM3
    發(fā)表于 12-20 06:29

    MATLAB編程風(fēng)格指南

    有關(guān) MATLAB代碼的建議通常強(qiáng)調(diào)的是效率,譬如說有關(guān)“不要用循環(huán)”等的建議,本指南與之不同。本指南主要考慮的是代碼(格式)的正確性、清晰性與通用性。本
    發(fā)表于 07-18 10:54 ?0次下載

    Altera代碼風(fēng)格講義--作者:駿龍小馬

    一個(gè)講解Altera代碼風(fēng)格的講義,適合初學(xué)者看看,verilog的代碼風(fēng)格
    發(fā)表于 11-17 18:07 ?0次下載

    讓你的 Python 代碼優(yōu)雅又地道

    Python社區(qū)文化的澆灌下,演化出了一種獨(dú)特的代碼風(fēng)格,去指導(dǎo)如何正確地使用Python,這就是常說的pythonic。一般說地道(idiomatic)的
    的頭像 發(fā)表于 03-06 10:35 ?3545次閱讀

    python代碼示例之基于Python的日歷api調(diào)用代碼實(shí)例

    本文檔的主要內(nèi)容詳細(xì)介紹的是python代碼示例之基于Python的日歷api調(diào)用代碼實(shí)例。
    發(fā)表于 09-06 14:25 ?42次下載
    <b class='flag-5'>python</b><b class='flag-5'>代碼</b>示例之基于<b class='flag-5'>Python</b>的日歷api調(diào)用<b class='flag-5'>代碼</b>實(shí)例

    Google編程風(fēng)格指南(一)

    使代碼易于管理的方法之一是加強(qiáng)代碼一致性. 讓任何程序員都可以快速讀懂你的代碼這點(diǎn)非常重要. 保持統(tǒng)一編程風(fēng)格并遵守約定意味著可以很容易根據(jù) “模式匹配” 規(guī)則來推斷各種標(biāo)識符的含義.
    的頭像 發(fā)表于 09-27 17:57 ?3027次閱讀

    Verilog HIDL的RTL設(shè)計(jì)風(fēng)格指南資源下載

    Verilog HIDL的RTL設(shè)計(jì)風(fēng)格指南資源下載
    發(fā)表于 04-13 10:09 ?9次下載

    C語言代碼風(fēng)格

    個(gè)人代碼風(fēng)格記錄此文將看到的一些好的代碼風(fēng)格規(guī)范總結(jié)起來,作為自己以后寫代碼時(shí)的參考。命名業(yè)界流行的3種命名
    發(fā)表于 01-13 13:13 ?1次下載
    C語言<b class='flag-5'>代碼</b><b class='flag-5'>風(fēng)格</b>

    什么樣的Verilog代碼風(fēng)格是好的風(fēng)格

    代碼是給別人和多年后的自己看的。 關(guān)于Verilog代碼設(shè)計(jì)的一些風(fēng)格和方法之前也寫過一些Verilog有什么奇技淫巧?
    的頭像 發(fā)表于 10-24 15:23 ?1359次閱讀

    [源代碼]Python算法詳解

    [源代碼]Python算法詳解[源代碼]Python算法詳解
    發(fā)表于 06-06 17:50 ?0次下載

    Yapf:一個(gè)格式化 Python 代碼的好幫手

    格式化為符合 PEP8 代碼指南的格式,還能格式化為符合 Google 代碼指南的格式,可選項(xiàng)更多,讓你的
    的頭像 發(fā)表于 10-17 11:08 ?631次閱讀
    Yapf:一個(gè)格式化 <b class='flag-5'>Python</b> <b class='flag-5'>代碼</b>的好幫手

    python軟件怎么運(yùn)行代碼

    Python是一種高級編程語言,它被廣泛用于開發(fā)各種類型的應(yīng)用程序,從簡單的腳本到復(fù)雜的網(wǎng)絡(luò)應(yīng)用和機(jī)器學(xué)習(xí)模型。要運(yùn)行Python代碼,您需要一個(gè)Python解釋器,它可以將您的
    的頭像 發(fā)表于 11-28 16:02 ?745次閱讀