您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關如何理解Google內部的Python代碼風格,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
Python是谷歌主要使用的動態語言,本風格指導列舉了使用Python編程時應該做和不該做的事項(dos & don'ts)
為了幫助你正確地組織代碼,我們編寫了一個Vim的設置文件.對于Emacs,默認設置即可.
許多團隊使用yapf自動格式工具來避免格式爭議
對代碼使用pylint
pylint
是一個用于在Python代碼中發現bug和代碼風格問題的工具,,pylint
查找那些常在非動態語言(例如C或C++)編譯器中捕獲的問題.由于Python是動態語言,一些警告可能不正確,不過應該非常少有錯誤警告.
能夠發現一些易被遺漏的錯誤,類似拼寫錯誤,調用早于聲明等等.
pylint
并不完美,為了更好的利用工具,我們有時候需要
a. Write around it(適配上下文風格)
b. 壓制一些警告
c. 優化工具
確保對代碼應用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有特別的意外)
一個模塊到另一個模塊之間共享代碼的復用性機制
命名空間管理約定簡單,每個標識的源都一致性地被指明了.例如x.Obj
表示Obj
是在模塊x
中定義的
模塊名可能會有沖突,一些模塊名可能很長,比較不方便
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
能夠避免模塊名沖突以及由于模塊搜索路徑與作者預期不符而造成的錯誤引用.讓查找模塊更簡單.
讓部署代碼時有些困難,因為包架構也需要賦值,不過對于現在的部署機制而言,這其實不是問題.
所有的新代碼都要從完整包名來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
異常處理是允許使用的,但使用務必謹慎
異常是一種從正常代碼段控制流中跳出以處理錯誤或者其他異常條件的手段.
正常代碼的控制流時不會被錯誤處理代碼影響的.異常處理同樣允許在某些情況下,控制流跳過多段代碼,例如在某一步從N個嵌入函數返回結果而非強行延續錯誤代碼.
可能會讓控制流變的難于理解,也比較容易錯過調用庫函數的報錯.
異常必定遵循特定條件:
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
避免全局變量
在模塊級別或者作為類屬性聲明的變量
有些時候有用
在import的過程中,有可能改變模塊行為,因為在模塊首次被引入的過程中,全局變量就已經被聲明
避免全局變量
作為技術變量,模塊級別的常量是允許并鼓勵使用的.例如MAX_HOLY_HANDGRENADE_COUNT = 3
, 常量必須由大寫字母和下劃線組成,參見下方命名規則
如果需要,全局變量需要在模塊級別聲明,并且通過在變量名前加_
來使其對模塊內私有化.外部對模塊全局變量的訪問必須通過公共模塊級別函數,參見下方命名規則
內嵌局部函數或類在關閉局部變量時是可以的.內部類意識可用的.(譯注:這里我的理解是當內嵌局部函數或類是和局部變量在同一個封閉作用域內是可以的.)
類可以在方法,函數,類內定義.函數可以在方法或函數內定義.內嵌函數對封閉作用域的變量具有只讀訪問權限.
允許定義只在非常有限作用域內可用的工具類或工具函數.Very ADT-y(??符合抽象數據類型要求???),通常用于實現裝飾器
內嵌或局部類的實例是不能被pickle的,內嵌函數或類是不能被直接測試的.嵌套會讓外部函數更長并且更難讀懂.
除了一些特別聲明,這些內嵌/局部/內部類和函數都是可以的.避免內嵌函數或類除了需要關閉一個局部值的時候.(譯者理解可能是除了將局部變量封閉在同一個作用域的情況以外).不要把一個函數轉為內嵌指示為了避免訪問.在這種情況下,把函數置于模塊級別并在函數名前加_
以保證測試是可以訪問該函數的.
在簡單情況下是可用的
List, Dict和Set推導生成式以及生成器表達式提供了一個簡明有效的方式來生成容器和迭代器而不需要傳統的循環,map()
,filter()
或者lambda表達式
簡單地推導表達比其他的字典,列表或集合生成方法更加簡明清晰.生成器表達式可以很有效率,因為完全避免了生成列表.
負載的推導表達式或生成器表達式很難讀懂
簡單情況下使用時可以的.每個部分(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)
對支持默認迭代器和云算法的類型例如列表,字典和文件等使用它們
容器類型(例如字典,列表等)定義了的默認的迭代器和成員檢查運算符.
默認迭代器和操作符是簡單有效的,能夠直接不需額外調用方法地表達操作.使用默認操作符的函數是通用的.能被用于任何支持這些操作的類型.
不能通過方法名來分辨類型,例如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(): ...
需要時使用生成器
生成器函數返回一個迭代器,每次執行yield
語句的時候生成一個值.在生成一個值之后,生成器函數的運行被掛起直到需要下一個值.
簡化代碼,因為局部變量和控制流在每次調用時被保留,生成器相比于一次性生成整個一個列表值要更節省內存.
無
建議使用.在生成器函數的文檔字符串中使用"Yields:"而非"Returns:"
單行代碼時是可以的
lambda在一個表達式內定義了匿名函數,而不在語句里.lambda表達式常被用于定義高階函數(例如map()
和filter()
)使用的回調函數或者操作符.
方便
比局部函數更難讀懂和debug,匿名意味著堆棧跟蹤更難懂.表達性受限因為lambda函數只包含一個表達式
對于單行代碼而言,可以使用lambda表達式.如果lambda
表達式內的代碼超過60-80個字符,最好定義成為常規的內嵌函數.
對于一般的操作諸如乘法,使用operator
模塊內置函數而非重新定義匿名函數,例如使用operator.mul
而非lambda x,y: x * y
簡單情況下可以使用.
條件表達式(也稱為三元運算符)是一種更短替代if語句的機制.例如x = 1 if cond else 2
相對于if語句更短也更方便
比if語句可能更難讀懂,當表達式很長的時候條件部分可能很難定位.
簡單情況可以使用.每個部分(真值表達式,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
在函數參數列表的最后可以為變量設定值,例如def foo(a, b=0):
.如果foo
在調用時只傳入一個參數,那么b
變量就被設定為0,如果調用時傳入兩個參數,那么b
就被賦予第二個參數值.
通常一個函數可能會有大量默認值,但是很少會有需要修改這些默認值的時候.默認值就提供了一個很簡單滿足上述情況的方式,而不需要為這些少見的情況重新定義很多函數.因為Python不支持重載方法或函數,默認參數是一個很簡單的方式來"假重載"行為.
默認參數在模塊加載時就被復制.這在參數是可變對象(例如列表或字典)時引發問題.如果函數修改了這些可變對象(例如向列表尾添加元素).默認值就被改變了.
使用時請注意以下警告----在函數或方法定義時不要將可變對象作為默認值.
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 仍可傳入未檢查的代碼(此處翻譯可能有誤)
...
使用屬性可以通過簡單而輕量級的訪問器和設定器方法來訪問或設定數據.
一種裝飾器調用來在計算比較輕量級時作為標準的屬性訪問來獲取和設定一個屬性的方式
對于簡單的屬性訪問,減少顯式的get和set方法能夠提升可讀性.允許惰性計算.被認為是一種Python化的方式來維護類接口.在表現上,當直接對變量的訪問更合理時,允許屬性繞過所需的瑣碎的訪問方法.
在Python2中必須繼承于object
,可能會隱藏像是操作符重載之類的副作用.對于子類而言,屬性可能有些迷惑性.
在通常會有簡單而且輕量級的訪問和設定方法的新代碼里使用屬性來訪問或設定數據.屬性在創建時被@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語句
在布爾環境下,Python對某些值判定為False,一個快速的經驗規律是所有"空"值都被認為是False,所以0, None, [], {}, ''
的布爾值都是False
使用Python布爾類型的條件語句可讀性更好而且更難出錯,大多數情況下,這種方式也更快.
對于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):
.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):
x = x or []
盡可能利用字符串方法而非string
模塊.使用函數調用語法而非apply
.在函數參數本就是一個行內匿名函數的時候,使用列表推導表達式和for循環而非filter
和map
當前Python版本提供了人們普遍更傾向的構建方式.
我們不使用任何不支持這些特性的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)
可以使用
一個內嵌Python函數可以引用在閉包命名空間內定義的變量,但是不能對其復制.變量綁定是解析到使用詞法作用域的,即基于靜態程序文本.任何對塊內命名的賦值都會讓Python將對于這個命名的引用都作為局部變量,即使在使用先于賦值的情況下也是.如果有全局聲明,這個命名就會被認為是全局變量.
一個使用這個特性的例子是:
def get_adder(summand1):
"""Returns a function that adds numbers to a given number."""
def adder(summand2):
return summand1 + summand2
return adder
經常可以讓代碼更簡明優雅,尤其會讓有經驗的Lisp和Scheme(以及Haskell和ML還有其他)的程序要很舒服.
可能會導致令人迷惑的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
.
可以使用
在明顯有好處時,謹慎明智的使用,避免@staticmethod
,控制使用@classmethod
函數和方法裝飾器(也就是@
記號).一個常見的裝飾器是@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)
能夠優雅的對方法進行某種轉換,而該轉換可能減少一些重復代碼并保持不變性等等.
裝飾器可以對函數的參數和返回值任意操作,導致非常隱形的操作行為.此外,裝飾器在import的時候就被執行,裝飾器代碼的實效可能非常難恢復.
在有明顯好處的地方謹慎地使用裝飾器.裝飾器應該和函數遵守相同的import和命名指導規則.裝飾器的文檔應該清晰地聲明該函數為裝飾器函數.并且要為裝飾器函數編寫單元測試.
避免裝飾器自身對外部的依賴,(如不要依賴于文件,socket,數據庫連接等等),這是由于在裝飾器運行的時候(在import時,可能從pydoc
或其他工具中)這些外部依賴可能不可用.一個被傳入有效參數并調用的裝飾器應該(盡可能)保證在任何情況下都可用.
裝飾器是一種特殊的"頂級代碼",參見main
永遠不要使用@staticmethod
,除非不得不整合一個API到一個已有的庫,應該寫一個模塊等級的函數.
只在寫一個命名的構造器或者一個類特定的,修改必要的全局狀態(例如進程緩存等)的流程時使用@classmethod
.
不要依賴于內建類型的原子性
盡管Python內置數據類型例如字典等似乎有原子性操作,仍有一些罕見情況下,他們是非原子的(比如,如果__hash__
或者__eq__
被實現為Python方法),就不應該依賴于這些類型的原子性.也不應該依賴于原子變量賦值(因為這依賴于字典)
優先使用Queue模塊的Queue
類來作為線程之間通訊數據的方式.此外,要是用threading模塊和其locking primitives(鎖原語).了解條件變量的合理用法以便于使用threading.Condition
而非使用更低級的鎖.
盡量避免使用
Python是一種非常靈活的語言并且提供了很多新奇的特性,諸如定制元類,訪問字節碼,動態編譯,動態繼承,對象父類重定義,import hacks,反射(例如一些對于getattr()
的應用),系統內置的修改等等.
這些是非常強大的語言特性,可以讓程序更緊湊
使用這些新特性是很誘人的.但是并不絕對必要,它們很難讀很難理解.也很難debug那些在底層使用了不常見的特性的代碼.對于原作者而言可能不是這樣,但是再次看代碼的時候,可能比更長但是更直接的代碼要難.
避免在代碼中使用這些特性.
內部使用這些特性的標準庫和類是可以使用的(例如abc.ABCMeta
,collections.namedtuple
,和enum
)
__future__
importPython3已經可用了(譯者:目前Python2已經不受支持了),盡管不是每個項目都準備好使用Python3,所有的代碼應該兼容Python3并且在可能的情況下在Python3的環境下測試.
Python3是Python的重大改變,盡管現有代碼通常是Python2.7寫成的,但可以做一些簡單的事情來讓代碼更加明確地表達其意圖,從而可以讓代碼更好地在Python3下運行而不用調整.
在考慮Python3編寫的代碼更清晰明確,一旦所有依賴已就緒,就可以更容易在Python3環境下運行.
一些人會認為默認樣板有些丑,import實際不需要的特性到模塊中是不常見的.
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文件.
類型注釋(也稱為"類型提示")是用于函數或方法參數和返回值的:
def func(a: int) -> List[int]:
你也可以聲明用一個單獨的注釋來聲明變量的類型:
a = SomeFunc() # type: SomeType
類型注釋提升代碼的可讀性和可維護性,類型檢查會將很多運行錯誤轉化為構建錯誤,也減少了使用過于強力特性的能力.
需要不斷更新類型聲明,對于認為有效的代碼可能會報類型錯誤,使用類型檢查可能減少使用過于強力特性的能力.
強烈鼓勵在更新代碼的時候進行Python類型分析.在對公共API進行補充和修改時,包括python類型聲明并通過構建系統中的pytype進行檢查.對Python來說靜態類型檢查比較新,我們承認,一些意料外的副作用(例如錯誤推斷的類型)可能拒絕一些項目的使用.這種情況下,鼓勵作者適當地增加一個帶有TODO或到bug描述當前不接搜的類型注釋的鏈接到BUILD文件或者在代碼內.
不要在行尾加分號,也不要用分號把兩行語句合并到一行
最大行長度是80個字符
超出80字符的明確例外:
# 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,
...
}
關于在一序列元素中的尾號逗號,只推薦在容器結束符號]
,)
或者}
和最后元素不在同一行時使用.尾后逗號的存在也被用作我們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, y = y, x
No:
if x == 4 :
print(x , y)
x , y = 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忽略/只在會被直接運行的文件里有必要寫.
確保使用正確的模塊,函數,方法的文檔字符串和行內注釋.
Python使用文檔字符串來為代碼生成文檔.文檔字符串是包,模塊,類或函數的首個語句.這些字符串能夠自動被__doc__
成員方法提取并且被pydoc
使用.(嘗試在你的模塊上運行pydoc
來看看具體是什么).文檔字符串使用三重雙引號"""
(根據PEP-257).文檔字符串應該這樣組織:一行總結(或整個文檔字符串只有一行)并以句號,問好或感嘆號結尾.隨后是一行空行,隨后是文檔字符串,并與第一行的首個引號位置相對齊.更多具體格式規范如下.
每個文件都應包含許可模板.選擇合適的許可模板用于項目(例如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()
"""
在本節,"函數"所指包括方法,函數或者生成器.
函數應有文檔字符串,除非符合以下所有條件:
文檔字符串應該包含足夠的信息以在無需閱讀函數代碼的情況下調用函數.文檔字符串應該是敘事體("""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.
"""
類定義下一行應為描述這個類的文檔字符串.如果類有公共屬性,應該在文檔字符串中的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."""
最后要在代碼中注釋的地方是代碼技巧性的部分.如果你將要在下次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(他只是不知道你試圖做什么).
注意標點,拼寫和語法,寫得好的注釋要比寫得差的好讀.
注釋應當是和敘事性文本一樣可讀,并具有合適的大小寫和標點.在許多情況下,完整的句子要比破碎的句子更可讀.更簡短的注釋如行尾的注釋有時會不太正式,但是應該全篇保持風格一致.
盡管被代碼審核人員指出在應該使用分號的地方使用了逗號是很令人沮喪的,將源代碼維護在高度清楚可讀的程度是很重要的.合適的標點,拼寫和語法能夠幫助達到這個目標.
如果類并非從其他基類繼承而來,那么就要明確是從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在文件對象被銷毀的同時被自動關閉的時候,是不可能將文件的生命周期和文件狀態綁定的:
推薦使用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和常量前面.應按照從最通用到最不通用的順序排列分組:
Python未來版本import語句,例如:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
更多信息參看上文
Python標準基礎庫import,例如:
import sys
第三方庫或包的import,例如:
import tensorflow as tf
代碼庫內子包import,例如:
from otherproject.ai import mind
此條已棄用:和當前文件是同一頂級子包專用的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
作為文件拓展名,不要使用橫線.
-
橫線,不應出現在任何包名或模塊名內__double_leading_and_trailing_underscore__
首尾都雙下劃線的名字,這種名字是python的內置保留名字_
)開頭表示是被保護的( from module import *
不會import).雙下劃線( __
也就是"dunder")開頭的實例變量或者方法表示類內私有(使用命名修飾).我們不鼓勵使用,因為這會對可讀性和可測試性有削弱二期 并非真正
的私有.import StringIO
還是 from StringIO import StringIO
?)test
開頭來分割名字的組成部分,即使這些組成部分是使用大寫字母駝峰式的.這種方式是可以的: test<MethodUnderTest>_<state>
例如 testPop_EmptyStack
,對于命名測試方法沒有明確的正確方法.文件拓展名必須為.py
,不可以包含-
.這保證了能夠被正常import和單元測試.如果希望一個可執行文件不需要拓展名就可以被調用,那么建立一個軟連接或者一個簡單的bash打包腳本包括exec "$0.py" "$@"
.
類型 | 公共 | 內部 |
---|---|---|
包 | 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或者希望在幾個不同的上下文中使用它,那么請將函數拆解成若干個更小更可控的片段.
self
或者 cls
增加合適的類型信息.例如 @classmethod def create(cls: Type[T]) -> T: return cls()
Any
遵循現有的縮進規范
標注類型后,函數簽名多數都要是"每行一個參數".
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:
...
如果需要同一模塊內還未定義的類名,例如需要類聲明內部的類,或者需要在后續代碼中定義的類,那么使用類名的字符串來代替.
class MyClass(object):
def __init__(self,
stack: List["MyClass"]) -> None:
參考PEP-008,只有在同時需要類型注釋和默認值的時候在=
前后都加空格
Yes:
def func(a: int = 0) -> int:
...
No:
def func(a:int=0) -> int:
...
在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:
...
可以對復雜類型聲明別名,別名的名稱應為CapWorded,如果只用于當前模塊,應加下劃線私有化.
例如,如果帶有模塊名的類型名過長:
_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[Text, List[Tuple[int, int]]]
其他示例是復雜的嵌套類型和一個函數的多個返回變量(作為元組).
可以通過增加特殊行注釋# type: ignore
來禁止類型檢查.
pytype
對于明確的報錯有關閉選項(類似于lint):
# pytype: disable=attribute-error
對變量標注類型如果內部變量很難或者不可能指向,可以使用下述方式:
類型注釋:
在行尾增加以# type
開頭的注釋
a = SomeUndecoratedFunction() # type: Foo
注釋綁定:
在變量名和賦值之間用冒號和類型注明,和函數參數一致.
a: Foo = SomeUndecoratedFunction()
不像是列表只能包含單一類型,元組可以既只有一種重復類型或者一組不同類型的元素,后者常用于函數返回.
a = [1, 2, 3] # type: List[int]
b = (1, 2, 3) # type: Tuple[int, ...]
c = (1, "2", 3.5) # type: Tuple[int, Text, float]
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()
注釋字符串的合適類型是基于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的遷移過程.
對于從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
只在運行時一定要避免進行類型檢查的情況下使用條件import.不鼓勵使用這種模式.鼓勵使用其他替代方式諸如重構代碼以容許頂級import.
只用于類型注釋的import可以被歸于if TYPE_CHECKING:
代碼塊中.
import typing
if typing.TYPE_CHECKING:
import sketch
def f(x: "sketch.Sketch"): ...
由于類型檢查引發的循環依賴是一種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:
...
當注釋的時候,優先泛型類型專有類型參數,否則泛型的參數會被認為是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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。