您好,登錄后才能下訂單哦!
Google內部的Python代碼風格是什么樣的,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
Python是谷歌主要使用的動態語言,本風格指導列舉了使用Python編程時應該做和不該做的事項(dos & don'ts)
為了幫助你正確地組織代碼,我們編寫了一個Vim的設置文件.對于Emacs,默認設置即可.
許多團隊使用yapf自動格式工具來避免格式爭議
對代碼使用pylint
2.1.1Definition(以下都譯為定義)
pylint是一個用于在Python代碼中發現bug和代碼風格問題的工具,,pylint查找那些常在非動態語言(例如C或C++)編譯器中捕獲的問題.由于Python是動態語言,一些警告可能不正確,不過應該非常少有錯誤警告.
2.1.2 Pros
能夠發現一些易被遺漏的錯誤,類似拼寫錯誤,調用早于聲明等等.
2.1.3 Cons
pylint并不完美,為了更好的利用工具,我們有時候需要
a. Write around it(適配上下文風格)
b. 壓制一些警告
c. 優化工具
2.1.4 Decision(以下都譯為建議)
確保對代碼應用pylint
如果一些警告是不合適的,就抑制這些警告,這是為了讓其他警告不會被隱藏.為了壓制警告,可以設置行級別的注釋:
dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin
pylint警告包含標識名(empty-docstring),谷歌專有的警告以g-開頭.
如果抑制警告的原因在標識名稱中表述不夠清晰,請額外添加注解.
用這種方式來抑制警告的優點是我們能夠簡單地找到抑制的警告并且重新訪問這些警告.
可以通過下述方式來獲得pylint警告列表:
pylint --list-msgs
用下述方式來獲取某個特定消息的更多具體信息:
pylint --help-msg=C6409
優先使用pylint: disable而非舊方法(pylint: disable-msg)如果要抑制由于參數未使用的警告,可以在函數開頭del,并注釋為什么要刪除這些未使用參數,僅僅一句"unused"是不夠的:
def viking_cafe_order(spam, beans, eggs=None): del beans, eggs # Unused by vikings. return spam + spam + spa
其他可以用來抑制警告的方式包括用'_'作為未使用參數的標識,在參數名前增加'unused_',或者分配這些參數到'_'.這些方式是可以的,但是已經不鼓勵繼續使用.前兩種方式會影響到通過參數名傳參的調用方式,而最后一種并不能保證參數確實未被使用.
只在import包和模塊的時候使用import,而不要應用在單獨的類或函數.(這一條對于typing_module有特別的意外)
2.2.1 定義
一個模塊到另一個模塊之間共享代碼的復用性機制
2.2.2 Pros
命名空間管理約定簡單,每個標識的源都一致性地被指明了.例如x.Obj表示Obj是在模塊x中定義的
2.2.3 Cons
模塊名可能會有沖突,一些模塊名可能很長,比較不方便
2.2.4 建議
import x(當x是包或模塊)
from x import y (當x是包前綴,y是不帶前綴的模塊名)
from x import y as z (當有重復模塊名y或y過長不利于引用的時候)
import y as z (僅在非常通用的簡寫的時候使用例如import numpy as np)
以sound.effects.echo為例:
from sound.effects import echo...echo.EchoFilter(input, output, delay=0.7, atten=4)
不要使用相對引用,即便在同一包內,也使用完整包名import,這有助于避免無意重復import包.
從typing module和six.moves module import不適用上述規則
每一模塊都要從完整路徑import
2.3.1 Pros
能夠避免模塊名沖突以及由于模塊搜索路徑與作者預期不符而造成的錯誤引用.讓查找模塊更簡單.
2.3.2 Cons
讓部署代碼時有些困難,因為包架構也需要賦值,不過對于現在的部署機制而言,這其實不是問題.
2.3.3 建議
所有的新代碼都要從完整包名來import模塊
import示例應該像這樣:
Yes:
# Reference absl.flags in code with the complete name (verbose). # 在代碼中使用完整路徑調用absl.flags import absl.flagsfrom doctor.who import jodie FLAGS = absl.flags.FLAGS
# Reference flags in code with just the module name (common). # 在代碼中只用包名來調用flags from absl import flagsfrom doctor.who import jodie FLAGS = flags.FLAGS
No:(假設文件在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? # 不清楚作者想要哪個包以及最終import的是哪個包, # 實際的import操作依賴于受到外部參數控制的sys.path # 那么哪一個可能的jodie模塊是作者希望import的呢? import jodie
不應該假設主代碼所在路徑被包含在sys.path中,即使有些時候可以work.在上一例代碼中,我們應該認為import jodie指的是import一個叫做jodie的第三方包或者頂級目錄中的jodie,而非一個當前路徑的jodie.py
異常處理是允許使用的,但使用務必謹慎
2.4.1 定義
異常是一種從正常代碼段控制流中跳出以處理錯誤或者其他異常條件的手段.
2.4.2 Pros
正常代碼的控制流時不會被錯誤處理代碼影響的.異常處理同樣允許在某些情況下,控制流跳過多段代碼,例如在某一步從N個嵌入函數返回結果而非強行延續錯誤代碼.
2.4.3 Cons
可能會讓控制流變的難于理解,也比較容易錯過調用庫函數的報錯.
2.4.4 建議
異常必定遵循特定條件:
使用raise MyError('Error message')或者raise MyError(),不要使用兩段raise MyError, 'Error message'
當內置異常類合理的時候,盡量使用內置異常.例如:拋出ValueError來表示一個像是違反預設前提(例如傳參了一個負數給要求正數的情況)的程序錯誤發生.
不要使用assert來片段公共結構參數值.assert是用來確認內部計算正確性也不是用來表示一些預期外的事件發生的.如果異常是后續處理要求的,用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中提及, 因為這樣并適合保障對于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
庫或者包可能會定義各自的異常.當這樣做的時候,必須要繼承一個已經存在的異常類,異常類的名字應該以Error結尾,并且不應該引入重復(foo.FooError)
永遠不要用捕獲全部異常的except:語句,或者捕獲Exception或者StandardError除非:
Python在這個方面容忍度很高,并且except:語句會捕獲包括拼寫錯誤,sys.exit(),Ctrl+C終止,單元測試失敗和和所有你并沒有想到捕獲的其他異常.
再次拋出這個異常
在程序中異常不會繼續但是會被記錄以及消除(例如通過保護最外層的方式保護線程不會崩潰)的地方創造一個孤立點.
最精簡try/except表達式內部的代碼量,try代碼塊里的代碼體量越大,月可能會在你不希望拋出異常的代碼中拋出異常,進而在這種情況下,try/except掩蓋了一個真實的異常
使用finally來執行代碼,這些代碼無論是否有異常在try代碼塊被拋出都會被執行.這在清理(即關閉文件)時非常有用.
當捕獲了異常時,用as而不是逗號分段.
try: raise Error() except Error as error: pass
避免全局變量
2.5.1 定義
在模塊級別或者作為類屬性聲明的變量
2.5.2 Pros
有些時候有用
2.5.3 Cons
在import的過程中,有可能改變模塊行為,因為在模塊首次被引入的過程中,全局變量就已經被聲明
2.5.4 建議
避免全局變量
作為技術變量,模塊級別的常量是允許并鼓勵使用的.例如MAX_HOLY_HANDGRENADE_COUNT = 3, 常量必須由大寫字母和下劃線組成,參見下方命名規則
如果需要,全局變量需要在模塊級別聲明,并且通過在變量名前加_來使其對模塊內私有化.外部對模塊全局變量的訪問必須通過公共模塊級別函數,參見下方命名規則
內嵌局部函數或類在關閉局部變量時是可以的.內部類意識可用的.(譯注:這里我的理解是當內嵌局部函數或類是和局部變量在同一個封閉作用域內是可以的.)
2.6.1 定義
類可以在方法,函數,類內定義.函數可以在方法或函數內定義.內嵌函數對封閉作用域的變量具有只讀訪問權限.
2.6.2 Pros
允許定義只在非常有限作用域內可用的工具類或工具函數.Very ADT-y(??符合抽象數據類型要求???),通常用于實現裝飾器
2.6.3 Cons
內嵌或局部類的實例是不能被pickle的,內嵌函數或類是不能被直接測試的.嵌套會讓外部函數更長并且更難讀懂.
2.6.4 建議
除了一些特別聲明,這些內嵌/局部/內部類和函數都是可以的.避免內嵌函數或類除了需要關閉一個局部值的時候.(譯者理解可能是除了將局部變量封閉在同一個作用域的情況以外).不要把一個函數轉為內嵌指示為了避免訪問.在這種情況下,把函數置于模塊級別并在函數名前加_以保證測試是可以訪問該函數的.
在簡單情況下是可用的
2.7.1 定義
List, Dict和Set推導生成式以及生成器表達式提供了一個簡明有效的方式來生成容器和迭代器而不需要傳統的循環,map(),filter()或者lambda表達式
2.7.2 Pros
簡單地推導表達比其他的字典,列表或集合生成方法更加簡明清晰.生成器表達式可以很有效率,因為完全避免了生成列表.
2.7.3 Cons
負載的推導表達式或生成器表達式很難讀懂
2.7.4 建議
簡單情況下使用時可以的.每個部分(mapping表達式,filter表達式等)都應該在一行內完成.多個for條款或者filter表達式是不允許的.當情況變得很復雜的適合就使用循環.
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.1 定義
容器類型(例如字典,列表等)定義了的默認的迭代器和成員檢查運算符.
Pros
默認迭代器和操作符是簡單有效的,能夠直接不需額外調用方法地表達操作.使用默認操作符的函數是通用的.能被用于任何支持這些操作的類型.
Cons
不能通過方法名來分辨類型,例如has_key()意味著字典,當然這也是一種優勢.
建議
對于支持的類型諸如列表,字典和文件,使用默認迭代器和操作符.內置類型同樣定義了迭代器方法.優先使用這些方法而非那些返回列表的方法.除非能夠確定在遍歷容器的過程中不會改變容器.不要使用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.1 定義
生成器函數返回一個迭代器,每次執行yield語句的時候生成一個值.在生成一個值之后,生成器函數的運行被掛起直到需要下一個值.
2.9.2 Pros
簡化代碼,因為局部變量和控制流在每次調用時被保留,生成器相比于一次性生成整個一個列表值要更節省內存.
2.9.3 Cons
無
2.9.4 建議
建議使用.在生成器函數的文檔字符串中使用"Yields:"而非"Returns:"
單行代碼時是可以的
2.10.1 定義
lambda在一個表達式內定義了匿名函數,而不在語句里.lambda表達式常被用于定義高階函數(例如map()和filter())使用的回調函數或者操作符.
2.10.2 Pros
方便
2.10.3 Cons
比局部函數更難讀懂和debug,匿名意味著堆棧跟蹤更難懂.表達性受限因為lambda函數只包含一個表達式
2.10.4 建議
對于單行代碼而言,可以使用lambda表達式.如果lambda表達式內的代碼超過60-80個字符,最好定義成為常規的內嵌函數.
對于一般的操作諸如乘法,使用operator模塊內置函數而非重新定義匿名函數,例如使用operator.mul而非lambda x,y: x * y
簡單情況下可以使用.
2.11.1 定義
條件表達式(也稱為三元運算符)是一種更短替代if語句的機制.例如x = 1 if cond else 2
2.11.2 Pros
相對于if語句更短也更方便
2.11.3 Cons
比if語句可能更難讀懂,當表達式很長的時候條件部分可能很難定位.
2.11.4 建議
簡單情況可以使用.每個部分(真值表達式,if表達式,else表達式)必須在一行內完成.如果使用條件表達式很富的時候使用完整的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')
大多數情況下都OK
2.12.1 定義
在函數參數列表的最后可以為變量設定值,例如def foo(a, b=0):.如果foo在調用時只傳入一個參數,那么b變量就被設定為0,如果調用時傳入兩個參數,那么b就被賦予第二個參數值.
2.12.2 Pros
通常一個函數可能會有大量默認值,但是很少會有需要修改這些默認值的時候.默認值就提供了一個很簡單滿足上述情況的方式,而不需要為這些少見的情況重新定義很多函數.因為Python不支持重載方法或函數,默認參數是一個很簡單的方式來"假重載"行為.
2.12.3 Cons
默認參數在模塊加載時就被復制.這在參數是可變對象(例如列表或字典)時引發問題.如果函數修改了這些可變對象(例如向列表尾添加元素).默認值就被改變了.
2.12.4 建議
使用時請注意以下警告----在函數或方法定義時不要將可變對象作為默認值.
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??? 模塊被加載的時間??? ... 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.1 定義
一種裝飾器調用來在計算比較輕量級時作為標準的屬性訪問來獲取和設定一個屬性的方式
2.13.2 Pros
對于簡單的屬性訪問,減少顯式的get和set方法能夠提升可讀性.允許惰性計算.被認為是一種Python化的方式來維護類接口.在表現上,當直接對變量的訪問更合理時,允許屬性繞過所需的瑣碎的訪問方法.
2.13.3 Cons
在Python2中必須繼承于object,可能會隱藏像是操作符重載之類的副作用.對于子類而言,屬性可能有些迷惑性.
2.13.4 建議
在通常會有簡單而且輕量級的訪問和設定方法的新代碼里使用屬性來訪問或設定數據.屬性在創建時被@property裝飾,參加裝飾器
如果屬性本身未被重寫,帶有屬性的繼承可能不夠明晰,因而必須確保訪問方法是被間接訪問的,來確保子類的方法重載是被屬性調用的(使用Template Method DP,譯者:應是模板方法設計模式).
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
只要可能,就使用隱式False的if語句
2.14.1 定義
在布爾環境下,Python對某些值判定為False,一個快速的經驗規律是所有"空"值都被認為是False,所以0, None, [], {}, ''的布爾值都是False
2.14.2 Pros
使用Python布爾類型的條件語句可讀性更好而且更難出錯,大多數情況下,這種方式也更快.
2.14.3 Cons
對于C/C++開發者而言可能有些奇怪
建議
如果可能的話,使用隱式False.例如使用if foo:而非if foo != []:下面列舉了一些你應該牢記的警告:
使用if foo is None(或者if foo is not None)來檢查None.例如在檢查一個默認值是None的變量或者參數是否被賦予了其他值的時候,被賦予的其他值的布爾值可能為False.
不要用==來和布爾值為False的變量比較,使用if not x,如果需要區別False和None,那么使用鏈式的表達式如if not x and x is not None
對于序列(如字符串,列表,元組),利用空序列為False的事實,故而相應地使用if seq:和if not seq:而非if len(seq)或if not len(seq):.
在處理整數時,隱式的False可能會引入更多風險(例如意外地將None和0進行了相同的處理)你可以用一個已知是整形(并且不是len()的結果)的值和整數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): xx = x or []
盡可能利用字符串方法而非string模塊.使用函數調用語法而非apply.在函數參數本就是一個行內匿名函數的時候,使用列表推導表達式和for循環而非filter和map
2.15.1 定義
當前Python版本提供了人們普遍更傾向的構建方式.
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. 可以,沒有行內的lambda表達式 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.1 定義
一個內嵌Python函數可以引用在閉包命名空間內定義的變量,但是不能對其復制.變量綁定是解析到使用詞法作用域的,即基于靜態程序文本.任何對塊內命名的賦值都會讓Python將對于這個命名的引用都作為局部變量,即使在使用先于賦值的情況下也是.如果有全局聲明,這個命名就會被認為是全局變量.
一個使用這個特性的例子是:
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
經常可以讓代碼更簡明優雅,尤其會讓有經驗的Lisp和Scheme(以及Haskell和ML還有其他)的程序要很舒服.
2.16.3 Cons
可能會導致令人迷惑的bug例如這個基于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函數所獲取的值 print(i, end='') bar()
所以foo([1, 2, 3])會打印1 2 3 3而非1 2 3 4.
2.16.4 建議
可以使用
在明顯有好處時,謹慎明智的使用,避免@staticmethod,控制使用@classmethod
2.17.1 定義
函數和方法裝飾器(也就是@記號).一個常見的裝飾器是@property,用于將普通方法轉換成動態計算屬性.然而裝飾器語法也允許用戶定義裝飾器,尤其對于一些函數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
能夠優雅的對方法進行某種轉換,而該轉換可能減少一些重復代碼并保持不變性等等.
2.17.3 Cons
裝飾器可以對函數的參數和返回值任意操作,導致非常隱形的操作行為.此外,裝飾器在import的時候就被執行,裝飾器代碼的實效可能非常難恢復.
2.17.4 建議
在有明顯好處的地方謹慎地使用裝飾器.裝飾器應該和函數遵守相同的import和命名指導規則.裝飾器的文檔應該清晰地聲明該函數為裝飾器函數.并且要為裝飾器函數編寫單元測試.
避免裝飾器自身對外部的依賴,(如不要依賴于文件,socket,數據庫連接等等),這是由于在裝飾器運行的時候(在import時,可能從pydoc或其他工具中)這些外部依賴可能不可用.一個被傳入有效參數并調用的裝飾器應該(盡可能)保證在任何情況下都可用.
裝飾器是一種特殊的"頂級代碼",參見main
永遠不要使用@staticmethod,除非不得不整合一個API到一個已有的庫,應該寫一個模塊等級的函數.
只在寫一個命名的構造器或者一個類特定的,修改必要的全局狀態(例如進程緩存等)的流程時使用@classmethod.
不要依賴于內建類型的原子性
盡管Python內置數據類型例如字典等似乎有原子性操作,仍有一些罕見情況下,他們是非原子的(比如,如果__hash__或者__eq__被實現為Python方法),就不應該依賴于這些類型的原子性.也不應該依賴于原子變量賦值(因為這依賴于字典)
優先使用Queue模塊的Queue類來作為線程之間通訊數據的方式.此外,要是用threading模塊和其locking primitives(鎖原語).了解條件變量的合理用法以便于使用threading.Condition而非使用更低級的鎖.
盡量避免使用
2.19.1 定義
Python是一種非常靈活的語言并且提供了很多新奇的特性,諸如定制元類,訪問字節碼,動態編譯,動態繼承,對象父類重定義,import hacks,反射(例如一些對于getattr()的應用),系統內置的修改等等.
2.19.2 Pros
這些是非常強大的語言特性,可以讓程序更緊湊
2.19.3 Cons
使用這些新特性是很誘人的.但是并不絕對必要,它們很難讀很難理解.也很難debug那些在底層使用了不常見的特性的代碼.對于原作者而言可能不是這樣,但是再次看代碼的時候,可能比更長但是更直接的代碼要難.
2.19.4 定義
避免在代碼中使用這些特性.
內部使用這些特性的標準庫和類是可以使用的(例如abc.ABCMeta,collections.namedtuple,和enum)
Python3已經可用了(譯者:目前Python2已經不受支持了),盡管不是每個項目都準備好使用Python3,所有的代碼應該兼容Python3并且在可能的情況下在Python3的環境下測試.
2.20.1 定義
Python3是Python的重大改變,盡管現有代碼通常是Python2.7寫成的,但可以做一些簡單的事情來讓代碼更加明確地表達其意圖,從而可以讓代碼更好地在Python3下運行而不用調整.
2.20.2 Pros
在考慮Python3編寫的代碼更清晰明確,一旦所有依賴已就緒,就可以更容易在Python3環境下運行.
2.20.3 Cons
一些人會認為默認樣板有些丑,import實際不需要的特性到模塊中是不常見的.
2.20.4 建議
from future imports
鼓勵使用from __future__ import語句.所有新代碼都應該包含下述代碼,而現有代碼應該被更新以盡可能兼容:
from __future__ import absolute_import from __future__ import division from __future__ import print_function
如果你不太熟悉這些,詳細閱讀這些:絕對import,新的/除法行為,和print函數
請勿省略或移除這些import,即使在模塊中他們沒有在使用,除非代碼只用于Python3.最好總是在所有的文檔中都有從future的import,來保證不會在有人使用在后續編輯時遺忘.
有其他的from __future__import語句,看喜好使用.我們的建議中不包含unicode_literals因為其并無明顯優勢,這是由于隱式默認的編碼轉換導致其在Python2.7內很多地方被引入了,必要時,大多數代碼最好顯式的使用b''和u''btyes和unicode字符串表示.(譯者:這段翻譯可能不準確)
The six, future, or past libraries
當項目需要支持Python2和3時,根據需求使用six,future和past.
可以根據PEP-484對Python3代碼進行類型注釋,并且在build時用類型檢查工具例如pytype進行類型檢查.
類型注釋可以在源碼中或stub pyi file中.只要可能,注釋就應寫在源代碼中.對于第三方或拓展模塊使用pyi文件.
2.21.1 定義
類型注釋(也稱為"類型提示")是用于函數或方法參數和返回值的:
def func(a: int) -> List[int]:
你也可以聲明用一個單獨的注釋來聲明變量的類型:
a = SomeFunc() # type: SomeType
2.21.2 Pros
類型注釋提升代碼的可讀性和可維護性,類型檢查會將很多運行錯誤轉化為構建錯誤,也減少了使用過于強力特性的能力.
2.21.3 Cons
需要不斷更新類型聲明,對于認為有效的代碼可能會報類型錯誤,使用類型檢查可能減少使用過于強力特性的能力.
2.21.4 建議
強烈鼓勵在更新代碼的時候進行Python類型分析.在對公共API進行補充和修改時,包括python類型聲明并通過構建系統中的pytype進行檢查.對Python來說靜態類型檢查比較新,我們承認,一些意料外的副作用(例如錯誤推斷的類型)可能拒絕一些項目的使用.這種情況下,鼓勵作者適當地增加一個帶有TODO或到bug描述當前不接搜的類型注釋的鏈接到BUILD文件或者在代碼內.
不要在行尾加分號,也不要用分號把兩行語句合并到一行
最大行長度是80個字符
超出80字符的明確例外:
長import
注釋中的:URL,路徑,flags等
不包含空格不方便分行的模塊級別的長字符串常量
pylint的diable注釋使用(如# pylint: disable=invalid-name)
不要使用反斜杠連接,除非對于需要三層或以上的上下文管理器with語句
利用Python的implicit line joining inside parentheses, brackets and braces(隱式行連接方法--括號連接,包括(), [], {}).如果必要的話,也可在表達式外面額外添加一對括號.
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'):
當字符串不能在一行內完成時,使用括號來隱式連接行:
x = ('This will build a very long long ' 'long long long long long long string')
在注釋內,如有必要,將長URL放在其本行內:
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
在定義一個表達式超過三行或更多的with語句時,可以使用反斜杠來分行.對于兩行表達式,使用嵌套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)
注意上述例子中的縮進,具體參看縮進
在其他一行超過80字符的情況下,而且yapf自動格式工具也不能使分行符合要求時,允許超過80字符限制.
括號合理使用
盡管不必要,但是可以在元組外加括號.再返回語句或者條件語句中不要使用括號,除非是用于隱式的連接行或者指示元組.
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)
縮進用4個空格
縮進代碼段不要使用制表符,或者混用制表符和空格.如果連接多行,多行應垂直對齊,或者再次4空格縮進(這個情況下首行括號后應該不包含代碼).
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 # 縮進4個空格,首行括號后無內容 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 # 首行不允許有內容 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 關于尾后逗號
關于在一序列元素中的尾號逗號,只推薦在容器結束符號],)或者}和最后元素不在同一行時使用.尾后逗號的存在也被用作我們Python代碼自動格式化工具yapf的提示,在,最后元素之后出現的時候來自動調整容器元素到每行一個元素.
Yes:
golomb3 = [0, 1, 3] golomb4 = [ 0, 1, 4, 6, ]
No:
golomb4 = [ 0, 1, 4, 6 ]
在頂級定義(函數或類)之間要間隔兩行.在方法定義之間以及class所在行與第一個方法之間要空一行,def行后無空行,在函數或方法內你認為合適地方可以使用單空行.
遵守標準的空格和標點排版規則.
括號(),[],{}內部不要多余的空格.
Yes:
spam(ham[1], {eggs: 2}, [])
No:
spam( ham[ 1 ], { eggs: 2 }, [ ] )
逗號、分號、冒號前不要空格,但是在后面要加空格,除非是在行尾.
Yes:
if x == 4: print(x, y) x, yy = y, x
No:
if x == 4 : print(x , y) x , yy = y , x
在函數調用括號的前,索引切片括號前都不加空格.
Yes:
spam(1) dict['key'] = list[index]
No:
spam (1) dict ['key'] = list [index]
行尾不要加空格.
在賦值(=),比較(==,<,>,!=,<>,<=,>=,in,not in,is,is not),布爾符號(and,or,not)前后都加空格.視情況在算術運算符(+,-,*,/,//,%,**,@),前后加空格
Yes:
x == 1
No:
x<1
在關鍵字名參數傳遞或定義默認參數值的時候不要在=前后加空格,只有一個例外:當類型注釋存在時在定義默認參數值時=前后加空格
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)
不要用空格來做無必要的對齊,因為這會在維護時帶來不必要的負擔(對于:.#,=等等).
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, }
大部分.py文件不需要從#!行來開始.根據PEP-394,程序的主文件應該以#!/usr/bin/python2或#!/usr/bin/python3起始
這行被用于幫助內核找到Python解釋器,但是在導入模塊時會被Python忽略/只在會被直接運行的文件里有必要寫.
確保使用正確的模塊,函數,方法的文檔字符串和行內注釋.
3.8.1 文檔字符串
Python使用文檔字符串來為代碼生成文檔.文檔字符串是包,模塊,類或函數的首個語句.這些字符串能夠自動被__doc__成員方法提取并且被pydoc使用.(嘗試在你的模塊上運行pydoc來看看具體是什么).文檔字符串使用三重雙引號"""(根據PEP-257).文檔字符串應該這樣組織:一行總結(或整個文檔字符串只有一行)并以句號,問好或感嘆號結尾.隨后是一行空行,隨后是文檔字符串,并與第一行的首個引號位置相對齊.更多具體格式規范如下.
3.8.2 模塊
每個文件都應包含許可模板.選擇合適的許可模板用于項目(例如Apache 2.0,BSD,LGPL,GPL)
文檔應該以文檔字符串開頭,并描述模塊的內容和使用方法.
"""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 函數和方法
在本節,"函數"所指包括方法,函數或者生成器.
函數應有文檔字符串,除非符合以下所有條件:
外部不可見
非常短
簡明
文檔字符串應該包含足夠的信息以在無需閱讀函數代碼的情況下調用函數.文檔字符串應該是敘事體("""Fetches rows from a Bigtable.""")的而非命令式的("""Fetch rows from a Bigtable."""),除了@property(應與attribute使用同樣的風格).文檔字符串應描述函數的調用語法和其意義,而非實現.對比較有技巧的地方,在代碼中使用注釋更合適.
覆寫了基類的方法可有簡單的文檔字符串向讀者指示被覆寫方法的文檔字符串例如"""See base class.""".這是因為沒必要在很多地方重復已經在基類的文檔字符串中存在的文檔.不過如果覆寫的方法行為實際上與被覆寫方法不一致,或者需要提供細節(例如文檔中表明額外的副作用),覆寫方法的文檔字符串至少要提供這些差別.
一個函數的不同方面應該在特定對應的分節里寫入文檔,這些分節如下.每一節都由以冒號結尾的一行開始, 每一節除了首行外,都應該以2或4個空格縮進并在整個文檔內保持一致(譯者建議4個空格以維持整體一致).如果函數名和簽名足夠給出足夠信息并且能夠剛好被一行文檔字符串所描述,那么可以忽略這些節.
Args:
列出每個參數的名字.名字后應有為冒號和空格,后跟描述.如果描述太長不能夠在80字符的單行內完成.那么分行并縮進2或4個空格且與全文檔一致(譯者同樣建議4個空格)
描述應該包含參數所要求的類型,如果代碼不包含類型注釋的話.如果函數容許*foo(不定長度參數列表)或**bar(任意關鍵字參數).那么就應該在文檔字符串中列舉為*foo和**bar.
Returns:(或對于生成器是Yields:)
描述返回值的類型和含義.如果函數至少返回None,這一小節不需要.如果文檔字符串以Returns或者Yields開頭(例如"""Returns row from Bigtable as a tuple of strings.""")或首句足夠描述返回值的情況下這一節可忽略.
Raises:
列出所有和接口相關的異常.對于違反文檔要求而拋出的異常不應列出.(因為這會矛盾地使得違反接口要求的行為成為接口的一部分)
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 類
類定義下一行應為描述這個類的文檔字符串.如果類有公共屬性,應該在文檔字符串中的Attributes節中注明,并且和函數的Args一節風格統一.
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中揭示代碼.應該現在就添加注釋.在復雜操作開始前,注釋幾行.對于不夠明晰的代碼在行尾注釋.
# 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.
為了提升易讀性,行注釋應該至少在代碼2個空格后,并以#后接至少1個空格開始注釋部分.
另外,不要描述代碼,假定閱讀代碼的人比你更精通Python(他只是不知道你試圖做什么).
3.8.6 標點,拼寫和語法
注意標點,拼寫和語法,寫得好的注釋要比寫得差的好讀.
注釋應當是和敘事性文本一樣可讀,并具有合適的大小寫和標點.在許多情況下,完整的句子要比破碎的句子更可讀.更簡短的注釋如行尾的注釋有時會不太正式,但是應該全篇保持風格一致.
盡管被代碼審核人員指出在應該使用分號的地方使用了逗號是很令人沮喪的,將源代碼維護在高度清楚可讀的程度是很重要的.合適的標點,拼寫和語法能夠幫助達到這個目標.
如果類并非從其他基類繼承而來,那么就要明確是從object繼承而來,即便內嵌類也是如此.
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正確運行并且保護代碼在Python3下出現潛在的不兼容.這樣也定義了object包括__new__,__init__,__delattr__,__getattribute__,__setattr__,__hash__,__repr__,和__str__等默認特殊方法的實現.
使用format或%來格式化字符串,即使參數都是字符串對象,也要考慮使用+還是%及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>'
避免使用+和+=操作符來在循環內累加字符串,因為字符串是不可變對象.這會造成不必要的臨時變量導致運行時間以四次方增長而非線性增長.應將每個字符串都記入一個列表并使用''.join來將列表在循環結束后連接(或將每個子字符串寫入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>'
在同一個文件內,字符串引號要一致,選擇''或者""并且不要改變.對于需要避免\\轉義的時候,可以更改.
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.")
多行字符串多行字符串優先使用"""而非''',當且只當對所有非文檔字符串的多行字符串都是用'''而且對正常字符串都使用'時才可使用三單引號.docstring不論如何必須使用"""
多行字符串和其余代碼的縮進方式不一致.如果需要避免在字符串中插入額外的空格,要么使用單行字符串連接或者帶有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 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.""")
當使用結束后顯式地關閉文件或socket.
不必要地打開文件,socket或其他類似文件的對象有很多弊端:
他們可能會消耗有限的系統資源,例如文件描述符.如果在使用沒有即使歸還系統,處理很多這樣對象的代碼可能會浪費掉很多不應浪費的資源.
保持一個文件可能會阻止其他操作諸如移動或刪除.
被程序共享的文件和socket可能會無意中在邏輯上已被關閉的情況下仍被讀寫.如果實際上已經關閉,試圖讀寫的操作會拋出異常,這樣就可以立即發現問題.
此外,當文件或socket在文件對象被銷毀的同時被自動關閉的時候,是不可能將文件的生命周期和文件狀態綁定的:
不能保證何時會真正將文件對象銷毀.不同的Python解釋器使用的內存管理技術不同,例如延時垃圾處理可能會讓對象的生命周期被無限期延長.
可能導致意料之外地對文件對象的引用,例如在全局變量或者異常回溯中,可能會讓文件對象比預計的生命周期更長.
推薦使用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)
對于下述情況使用TODO注釋:臨時的,短期的解決方案或者足夠好但是不完美的解決方案.
TODO注釋以全部大寫的字符串TODO開頭,并帶有寫入括號內的姓名,email地址,或其他可以標識負責人或者包含關于問題最佳描述的issue.隨后是這里做什么的說明.
有統一風格的TODO的目的是為了方便搜索并了解如何獲取更多相關細節.TODO并不是保證被提及者會修復問題.因此在創建TODO注釋的時候,基本上都是給出你的名字.
# TODO(kl@gmail.com): Use a "*" here for string repetition. # TODO(Zeke) Change this to use relations.
如果TODO注釋形式為"未來某個時間點會做什么事"的格式,確保要么給出一個非常具體的時間點(例如"將于2009年11月前修復")或者給出一個非常具體的事件(例如"當所有客戶端都能夠處理XML響應時就移除此代碼").
imports應該在不同行.例如:
Yes:
import os import sys
No:
import os, sys
import應集中放在文件頂部,在模塊注釋和docstring后面,模塊globals和常量前面.應按照從最通用到最不通用的順序排列分組:
1. Python未來版本import語句,例如:
from __future__ import absolute_import from __future__ import division from __future__ import print_function
更多信息參看上文
2. Python標準基礎庫import,例如:
import sys
3. 第三方庫或包的import,例如:
import tensorflow as tf
4. 代碼庫內子包import,例如:
from otherproject.ai import mind
5. 此條已棄用:和當前文件是同一頂級子包專用的import,例如:
from myproject.backend.hgwells import time_machine
在舊版本的谷歌Python代碼風格指南中實際上是這樣做的.但是現在不再需要了.**新的代碼風格不再受此困擾.**簡單的將專用的子包import和其他子包import同一對待即可.
在每個組內按照每個模塊的完整包路徑的字典序忽略大小寫排序.可以根據情況在每個節質檢增加空行.
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: # 舊版本代碼風格可能會采用下述import方式 # from myproject.backend.hgwells import time_machine # from myproject.backend.state_machine import main_loop
每行只有一條語句.
不過如果測試語句和結果能夠在一行內放下,就可以放在一行內.但是不允許將try/except語句和對應內容放于一行,因為try或者except都不能在一行內完成.對于沒有else的if語句可以將if和對應內容合并到一行.
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)
對于瑣碎又不太重要的訪問函數,應用公共變量來替代訪問函數,以避免額外的程序調用消耗,當添加了更多函數功能時,使用property來保持連續性
此外,如果訪問過于復雜,或者訪問變量的消耗過大,應該使用諸如get_foo()和set_foo()之類的函數式訪問(參考命名指南).如果過去的訪問方式是通過屬性,新訪問函數不要綁定到property上,這樣使用property的舊方式就會失效,使用者就會知道函數有變化.
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.
命名函數名,變量名,文件名應該是描述性的,避免縮寫,尤其避免模糊或對讀者不熟悉的縮寫.并且不要通過刪減單詞內的字母來縮短.
使用.py作為文件拓展名,不要使用橫線.
3.16.1 要避免的名字:
單字符名字,除非是計數或迭代元素,e可以作為Exception捕獲識別名來使用..
-橫線,不應出現在任何包名或模塊名內
__double_leading_and_trailing_underscore__首尾都雙下劃線的名字,這種名字是python的內置保留名字
3.16.4 命名約定
internal表示僅模塊內可用、或者類內保護的或者私有的
單下劃線(_)開頭表示是被保護的(from module import *不會import).雙下劃線(__也就是"dunder")開頭的實例變量或者方法表示類內私有(使用命名修飾).我們不鼓勵使用,因為這會對可讀性和可測試性有削弱二期并非真正的私有.
相關的類和頂級函數放在同一個模塊內,不必像是Java一樣要一個類放在一個模塊里.
對類名使用大寫字母(如CapWords)開頭的單詞,命名,模塊名應該使用小寫加下劃線的方式.盡管有一些舊的模塊命名方式是大寫字母的(如CapWords.py),現在不鼓勵這樣做了,因為在模塊剛好是從某個類命名出發的時候可能會令人迷惑(例如是選擇import StringIO還是from StringIO import StringIO?)
在unittest方法中可能是test開頭來分割名字的組成部分,即使這些組成部分是使用大寫字母駝峰式的.這種方式是可以的:test<MethodUnderTest>_<state>例如testPop_EmptyStack,對于命名測試方法沒有明確的正確方法.
3.16.3 文件名
文件拓展名必須為.py,不可以包含-.這保證了能夠被正常import和單元測試.如果希望一個可執行文件不需要拓展名就可以被調用,那么建立一個軟連接或者一個簡單的bash打包腳本包括exec "$0.py" "$@".
3.16.4 Guido的指導建議
類型 | 公共 | 內部 |
---|---|---|
包 | lower_with_under | |
模塊 | lower_with_under | _lower_with_under |
類 | CapWords | _CapWords |
異常 | CapWords | |
函數 | lower_with_under() | _lower_with_under() |
全局/類常量 | CAPS_WITH_UNDER | _CAPS_WITH_UNDER |
全局/類變量 | lower_with_under | _lower_with_under |
實例變量 | lower_with_under | _lower_with_under (受保護) |
方法名 | lower_with_under() | _lower_with_under() (受保護) |
函數/方法參數 | lower_with_under | |
局部變量 | lower_with_under |
盡管Python支持通過雙下劃線__(即"dunder")來私有化.不鼓勵這樣做.優先使用單下劃線.單下劃線更易于打出來、易讀、易于小的單元測試調用.Lint的警告關注受保護成員的無效訪問.
即便是一個用做腳本的py文件也應該是可以被import的,而只用于import時,也不應有執行了主函數的副作用.主函數的功能應該被放在main()里.
在Python中,pydoc和單元測試要求模塊是可import的.所以代碼在主程序執行前應進行if __name__ == '__main__':檢查,以防止模塊在import時被執行.
def main(): ... if __name__ == '__main__': main()
所有頂級代碼在模塊被import時執行.因而要小心不要調用函數,創建對象或者執行其他在執行pydoc時不應該被執行的操作.
優先寫小而專一的函數.
長函數有時候是合適的,故而函數長度沒有固定的限制.但是超過40行的時候就要考慮是否要在不影響程序結構的前提下分解函數.
盡管長函數現在運行的很好,但是在之后的時間里其他人修改函數并增加新功能的時候可能會引入新的難以發現的bug,保持函數的簡短,這樣有利于其他人讀懂和修改代碼.
在處理一些代碼時,可能會發現有些函數長而且復雜.不要畏懼調整現有代碼,如果處理這個函數非常困難,如難以對報錯debug或者希望在幾個不同的上下文中使用它,那么請將函數拆解成若干個更小更可控的片段.
3.19.1 基本規則
熟悉PEP-484
在方法中,只在必要時給self或者cls增加合適的類型信息.例如@classmethod def create(cls: Type[T]) -> T: return cls()
如果其他變量或返回類型不定,使用Any
不需要注釋每個函數
至少需要注明公共接口
使用類型檢查來在安全性和聲明清晰性以及靈活性之間平衡
標注容易因類型相關而拋出異常的代碼(previous bugs or complexity,此處譯者認為是與上一條一致,平衡安全性和復雜性)
標注難理解的代碼
標注類型穩定的代碼,成熟穩定的代碼可以都進行標注而不會影響其靈活性
3.19.2 分行
遵循現有的縮進規范
標注類型后,函數簽名多數都要是"每行一個參數".
def my_method(self, first_var: int, second_var: Foo, third_var: Optional[Bar]) -> int: ...
優先在變量之間換行,而非其他地方(如變量名和類型注釋之間).如果都能放在一行內,就放在一行.
def my_method(self, first_var: int) -> int: ...
如果函數名,一直到最后的參數以及返回類型注釋放在一行過長,那么分行并縮進4個空格.
def my_method( self, first_var: int) -> Tuple[MyLongType1, MyLongType1]: ...
當返回值類型不能和最后一個參數放入同一行,比較好的處理方式是將參數分行并縮進4個空格,右括號和返回值類型換行并和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]: ...
就像上面的例子一樣,盡量不要分割類型注釋,不過有時類型注釋太長無法放入一行,(那就盡量讓子注釋不要被分割).
def my_method( self, first_var: Tuple[List[MyLongType1], List[MyLongType2]], second_var: List[Dict[ MyLongType3, MyLongType4]]) -> None: ...
如果某個命名和類型太長了,考慮使用別名.如果沒有其他解決方案,在冒號后分行縮進4個空格.
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 前置聲明
如果需要同一模塊內還未定義的類名,例如需要類聲明內部的類,或者需要在后續代碼中定義的類,那么使用類名的字符串來代替.
class MyClass(object): def __init__(self, stack: List["MyClass"]) -> None:
3.19.4 默認值
參考PEP-008,只有在同時需要類型注釋和默認值的時候在=前后都加空格
Yes:
def func(a: int = 0) -> int: ...
No:
def func(a:int=0) -> int: ...
3.19.5 NoneType
在Python系統中NoneType是一等類型,為了方便輸入,None是NoneType的別名.如果一個參數可以是None,那么就需要聲明!可以使用Union,但如果只有一個其他類型,那么使用Optional.
顯式地使用Optional而非隱式地.PEP 484的早期版本容許a: Text = None被解釋為a: Optional[Text] = None.但現在已經不推薦這樣使用了.
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 類型別名
可以對復雜類型聲明別名,別名的名稱應為CapWorded,如果只用于當前模塊,應加下劃線私有化.
例如,如果帶有模塊名的類型名過長:
_ShortName = module_with_long_name.TypeWithLongName ComplexMap = Mapping[Text, List[Tuple[int, int]]]
其他示例是復雜的嵌套類型和一個函數的多個返回變量(作為元組).
3.19.7 忽略類型檢查
可以通過增加特殊行注釋# type: ignore來禁止類型檢查.
pytype對于明確的報錯有關閉選項(類似于lint):
# pytype: disable=attribute-error
3.19.8 對變量注釋類型
對變量標注類型如果內部變量很難或者不可能指向,可以使用下述方式:
類型注釋:
在行尾增加以# type開頭的注釋
a = SomeUndecoratedFunction() # type: Foo
注釋綁定:
在變量名和賦值之間用冒號和類型注明,和函數參數一致.
a: Foo = SomeUndecoratedFunction()
3.19.9 元組和列表
不像是列表只能包含單一類型,元組可以既只有一種重復類型或者一組不同類型的元素,后者常用于函數返回.
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是有泛型的,工廠函數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模塊預定義好的類型變量是AnyStr,用于針對字符串可以是bytes也可為unicode并且保持一致的多個類型注釋.
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可能可用.當在不同Python版本之間返回值類型不同的時候通常是為了照顧兼容性.避免使用unicode,因為Python3中不存在.
No:
def py2_code(x: str) -> unicode: ...
對于處理二進制數據的代碼,請使用bytes.
Yes:
def deals_with_binary_data(x: bytes) -> bytes: ...
對于Python2兼容,處理文本數據(Python中str或unicode,Python3中str)的代碼,使用Text.對于只有Python3的代碼,優先使用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]: ...
如果一個函數中所有的字符串類型始終一致,例如前文例子中返回值類型和參數類型是一致的,那么使用AnyStr
像這樣寫能夠簡化代碼向Python3的遷移過程.
3.19.12 typing的import
對于從typing模塊import的類,要import類本身.明確的允許在一行內從typing模塊import多個特定的類,如
from typing import Any, Dict, Optional
這種從typing模塊import的方式會向命名空間內增加額外項,typing中的任何命名都應該和關鍵字同等對待并且不在你的Python代碼中定義,typed or not(譯者推測文無論是否引入).如果和已有的命名沖突,使用import x as y來import.
from typing import Any as AnyType
3.19.13 條件import
只在運行時一定要避免進行類型檢查的情況下使用條件import.不鼓勵使用這種模式.鼓勵使用其他替代方式諸如重構代碼以容許頂級import.
只用于類型注釋的import可以被歸于if TYPE_CHECKING:代碼塊中.
條件import的類型應被視為字符串引用,以和Python3.6兼容(在Python3.6中,注釋表達式實際上被賦值的).
只有單獨用于類型注釋的實例才能在這里定義,包括了別名.否則將會報運行錯誤因為在運行時這些模塊不會被引用.
代碼塊應該緊跟在正常import后面.
在類型import后不應有空行
按照正常import順序對這一塊代碼進行排序
import typing if typing.TYPE_CHECKING: import sketch def f(x: "sketch.Sketch"): ...
3.19.14 循環依賴
由于類型檢查引發的循環依賴是一種code smell(代碼異味),這樣的代碼應當被重構.盡管技術上是可以保留循環引用的.build system(系統)不允許這樣做因為每個模塊都要依賴于其他模塊.
將造成循環依賴的模塊替換為Any并賦予一個有意義的別名并使用從這個模塊導入的真實類名(因為任何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 泛型
當注釋的時候,優先泛型類型專有類型參數,否則泛型的參數會被認為是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: ...
如果泛型最佳的參數類型是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."""
如果你在編輯代碼,花幾分鐘看看現有代碼然后決定好要使用哪種風格.如果現有代碼在所有算術運算符兩側都加了空格,那么你也應該如此.如果現有的注釋用井號組成了包圍框,那么你的注釋也應如此.
有代碼風格指南的目的是有一個編程的共識,這樣人們能夠集中在內容而非形式上.我們將通用的代碼風格指南公布于此這樣人們就能了解這個共識(譯者:有巴別塔的意味.)但是各自的代碼風格也很重要.如果你添加的代碼與原有代碼看起來完全不一致,就會打亂讀者的閱讀節奏,避免這樣。
關于Google內部的Python代碼風格是什么樣的問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。