Google python code style


Posted by Kled on 2021-12-22

Google python code style

Google Python Style Guide
Google Python Style 中文版

Lint

使用pylint來debug, 但因為python是動態編譯, 所以不像C, C++有些問題可以在編譯抓到

優點 : 可以捕獲容易忽視的錯誤, 輸入錯誤, 未賦值的變數等
缺點 : 因為pylint不完美, 有時候需要圍繞著pylint來寫代碼, 抑制其警告, 或者忽略

像是下面的寫法來抑制pylint警告

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

要抑制”參數未使用”告警, 你可以用””作為參數標識符, 或者在參數名前加”unused”.

Imports

針對packages跟modules import, 不要針對個別的classes或function import

module共享代碼的重用機制

優點 : 空間管理定義十分簡單
缺點 : module名稱可能衝突, 有些module名稱太長不方便

  • 使用import x來導入modules跟packages
  • 使用from x import y, x是package前綴, y是不帶前綴的module name
  • 使用from x import y as z, 如果發生衝突, 或者名稱太長

例如module sound.effects.echo可以用下面的方式

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

Package

使用module的全路徑來導入module

優點 : 避免module name衝突, 查找module更容易
缺點 : 部屬代碼變男, 因為必須複製package的層次

Exceptions

Exception是一種跳出代碼塊的正常流程控制, 來處理錯誤或異常條件的方法

優點 : 正常操作代碼的控制流, 不會和錯誤處理混在一起, 當某種條件發生時, 允許跳過多個框架
缺點 : 可能會導致讓人困惑的控制流, 調用時容易錯誤錯誤情況

錯誤必須遵守特定條件 :

  1. 像這樣觸發異常raise MyException("Error message") 或者 raise MyException, 不要使用兩個參數的形式(raiseu MyException, "Error message")或者只拋出字串(raise "Error message")
  2. module or package應該定義自己的特定域的異常類, 這個異常類應該從內建的Exception繼承, module的異常基類應該叫做已Error結尾
    class ABCDError(Exception):
     pass
    
  3. 永遠不要用except:來捕獲所有異常, 也不要捕獲Exception或者StandardError, 除非是為了重新引發異常或程序中建立一個隔離點, 異常通常不會傳播, 例如通過保護其外層免於程式crash
  4. 不要使用assert來驗證公共API的參數值ValueError, assert用於確保程式內部邏輯正確性, 而不是強制正確使用, 這種情況使用raise
  5. 盡量減少try/except量, try的體積越大, 期望之外的異常就越容易被觸發
  6. 使用finally來執行那些無論try有沒有異常都該被執行的代碼, 這可以用於清理資源, 例如關閉檔案

Global variable

避免全局變量

在module級別或作為class屬性聲明的變量
優點 : 偶爾有用
缺點 : 有可能在導入期間更改module行為, 因為對全局變量在第一次導入module就完成了

但允許並鼓勵使用module constant, 例如 : _MAX_HOLY_HANDGRENADE_COUNT = 3, 必須由帶下畫線的開頭大寫字母命名

透過下畫線可以將其至於module內部

Nested/Local/Inner Classes and Functions

local function or classes是fine的, 當它們只使用在local 區域內
類別可以定義在function, method, class裡面, function可以定義在method, function內, 巢狀結構只能access read-only變數

優點 : 允許定義有效範圍的工具
缺點 : 局部類的實例不能序列化(pickled)

未了實現區域化變數定義, 是建議使用這種方式的

Comprehensions & Generator Expressions

list comprehensions和generator提供了一種簡潔高效的方式來創建列表和iterator, 而不必借助map, filter, lambda

優點 : 簡單的列表推導可以比其他的列表創建更加簡單, 生成器表達式十分高效, 因為他們避免了創建整個列表
缺點 : 複雜的列表推倒或生成器會難以閱讀

適用於簡單情況, 每個部份應該單獨置於一行, 映射表達式, for, filter表達式, 禁止用在多重for語句或過濾器表達式, 複雜的情況下還是使用循環

Yes

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

  for x in xrange(5):
      for y in xrange(5):
          if x != y:
              for z in xrange(5):
                  if y != z:
                      yield (x, y, z)

  return ((x, complicated_transform(x))
          for x in long_generator_function(parameter)
          if x is not None)

  squares = [x * x for x in range(10)]

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

No

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

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

Default Iterators and Operators

最好使用原本就支援的型別, list, dict, file

優點 : 默認操作符合iterator簡單高效, 直接表達了操作, 沒有額外的方法調用, 並且支援任何操作類型
缺點 : 沒辦法通過閱讀方法名來區分對象的類型, 例如has_key()意味著字典, 但這也代表著他對大家都通用

Yes

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

No

No:   for key in adict.keys(): ...
      if not adict.has_key(key): ...
      for line in afile.readlines(): ...

Generator

生成器函數, 每次透過(yield)產生return, 並且保持狀態, 等待下一次的yield

優點 : 簡化代碼, 每次調用時, 局部變量和控制流的狀態都會被保存, 比起一次創建一系列內存的函數, 生成器使用的內存更少
缺點 : 沒有

鼓勵使用, 生成器是使用yield, 而非return

Lambda

適用於單行函數

通常用於map()filter()這類的函數

優點 : 方便
缺點 : 難閱讀, 沒有函數名稱代表著更難理解

適用於60-80個字符的單行函數

Conditional Expressions

OK for simple case

優點 : 方便
缺點 : 難於閱讀

在單行情況下使用, 其他情況下使用完整的if語句

Yes

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

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

Default Argument Values

在參數最後指定變量的值使用def foo(a, b=0), 如果使用此function時只帶入一個參數, 則b會預設為0

優點 : 常會碰到需要一堆大量默認值的參數, python也不支持重載方法和函數, 默認參數是一種仿造重載行為的簡單模式
缺點 : 默認參數只在加載時求值一次, 如果參數式list 或dict的可變類型, 可能會導致問題, 如果函數修改了該list或dict, 那麼默認值會被改掉

鼓勵使用, 但不要用可變對象最為默認值

Yes

Yes: def foo(a, b=None):
         if b is None:
             b = []

No

No:  def foo(a, b=[]):
         ...
No:  def foo(a, b=time.time()):  # The time the module was loaded???
         ...
No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...
         ...

屬性(properties)

實體屬性

伴隨物件來建立, 也就是透過.來實現該物件的屬性(Attribute), 各物件的實體屬性各自獨立

class Cars:
    # 建構式
    def __init__(self, color, seat):
        self.color = color
        self.seat = seat
        self.weight = 140

類別屬性

定義在類別層級的屬性, 也就是在建構式之外的屬性
可以不需要透過建立Object, 可以直接透過類別名稱存取, 各物件共享Class Attribute值

# 汽車類別
class Cars:
    door = 4
    # 建構式
    def __init__(self, color, seat):
        self.color = color
        self.seat = seat
        self.weight = 140

Cars.door = 6

屬性(property)

是一個允許我們設定及取得屬性(Attribute)值的物件(Object),當我們想要對類別(Class)中的屬性(Attribute)有更多的控制時,就會使用Python的屬性(Property)來達成

# 汽車類別
class Cars:
    # 建構式
    def __init__(self, weight):
        self.weight = weight  #車重屬性
mazda = Cars(-200)

上面的例子, 編譯器雖然不會報錯, 但車不會有負的重量, 我們要如何防止來源端傳入不正確的資料?
所以這時候有寫過其他物件導向的人就會這麼做

# 汽車類別
class Cars:
    # 建構式
    def __init__(self, weight):
        self.set_weight(weight)
    def get_weight(self):
        return self.__weight
    def set_weight(self, value):
        if value <= 0:
            raise ValueError("Car weight cannot be 0 or less.")
        self.__weight = value
mazda = Cars(-200)

範例於建構式(Constructor)中,呼叫設定屬性(Attribute)的方法(Method)來設定其值,並於方法(Method)中判斷如果傳入值小於等於0時,丟出例外錯誤,否則就設定weight屬性(Attribute)值。由於我們傳入了負數,所以從執行結果可以看到ValueError的例外錯誤。

雖然此方法可以達到檢核的目的,但是這樣的寫法不"Pythonic",意思是沒有寫出Python的特點或風格,我們可以使用Python的屬性(Property)來達到相同的效果。如下範例:

# 汽車類別
class Cars:
    # 建構式
    def __init__(self, weight):
        self.weight = weight
    @property
    def weight(self):
        return self.__weight
    @weight.setter
    def weight(self, value):
        if value <= 0:
            raise ValueError("Car weight cannot be 0 or less.")
        self.__weight = value

在讀取屬性(Attribute)的方法(原get_weight()方法)上方加上@property Decorator,並且將方法名稱修改為weight,這個weight就是屬性(Property)。接著在設定屬性(Attribute)的方法(原set_weight()方法)上方加上@property.setter,也就是@weight.setter,意思就是告訴類別(Class)當來源端要設定屬性(Property)值時,要呼叫這個方法(Method)。同樣我們將方法名稱修改為weight,最後別忘了修改建構式(Constructor)中的屬性(Property)設定與原本一樣。

所以我們知道在物件導向中, 使用get, set是操作物件的標準形式, 我們可以透過property裝飾器簡化這一點

優點 : 通過消除簡單屬性的訪問, 可以增加可讀性, 就性能而言, 如果直接訪問變量是合理的, 那麼添加訪問方法(set/get)就顯得瑣碎而無意義, 使用properties可以繞過set/get遇到的問題, 且可以不破壞街口的情況下訪問
*缺點 : 繼承時可能會讓人困惑

原本應該是熟悉使用set/get, 但這裡建議使用@property裝飾器來創建

Yes: import math

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

         To use:
         >>> sq = Square(3)
         >>> sq.perimeter
         12
         """

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

         area = property(___get_area, ___set_area,
                         doc="""Gets or sets the area of the square.""")

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

還有其他的使用方式, 但直接使用@property裝飾器感覺比較快

True/False Evaluations

盡可能使用implicit false, 簡單來說就是所有的空值都會被認為是false (包含0, None, [], {})

優點 : 使用booling條件句更易讀也更不容易犯錯
缺點 : C/C++開發人員來說, 會看起來有點怪

盡可能使用implicit flase, 像是if foo:, 而不是if foo != []

  1. 永遠使用if foo is None: (or is not None)去檢查None, 否則如果出現False值, 會跟None的意義搞混
  2. 不要這樣使用boolean== False, 應該使用if not x and x is not None, 一樣是為了避免None之類的東西搞混
  3. 如果是一個list, tuple, string, 使用if seq:或者if not seq:, 不要使用if len(seq):, 以及if not len(seq):
  4. 處理整數時, 使用隱式false可能會得不償失(即不小心將None當做0來處理). 你可以將一個已知是整型(且不是len()的返回結果)的值與0比較
  5. 注意'0'則會被當成true

Yes

Yes: if not users:
         print 'no users'

     if foo == 0:
         self.handle_zero()

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

No

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()

Lexical Scoping (static scope) 詞法作用域

在function1包function2的過程中, 因為function2是被包在第一個function1內的, 所以在function2會認為function1的變數是全域變數, 所以是可以access function1的變數的, 可以看下例adder可以拿到get_addersummand1的值

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

    return adder

sum = get_adder(summand1)(summand2)

優點 : 通常可以帶來更清晰, 優雅的代碼
缺點 : 可能導致讓人迷惑的bug

例如下面會讓人迷惑

i = 4
def foo(x):
    def bar():
        print i,
    # ...
    # A bunch of code here
    # ...
    for i in x:  # Ah, i *is* local to Foo, so this is what Bar sees
        print i,
    bar()

foo([1, 2, 3]) #1 2 3 3

這時候的bar因為foo有定義i, 所以不會print到外面全域變數的i, 但通常不會發生這種事(如果其他變數定義的好的話)

google推薦使用

函數與方法裝飾器

最常見的裝飾器是@classmethodstaticmethod, 用於將常規函數轉換成類方法或靜態方法, 不過裝飾器語法也允許用戶自定義裝飾器, 下面兩個代碼是等效的

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

優點 : 優雅的在函數上指定一些轉換, 該轉換可以減少一些重複代碼, 保持已有函數不變
缺點 : 裝飾器可以在函數的參數或返回值上執行任何操作, 這可能導致隱藏的bug

好處很明顯, 需要謹慎的使用裝飾器, 應該遵守和函數一樣的導入和命名規則, 文檔應該清楚的說明該函數是一個裝飾器, 請為裝飾器編寫單元測試
避免裝飾器對外部的依賴(例如依賴文件, socket, 數據庫連接), 應該保證一個有效參數調用的裝飾器在所有情況下都是成功的

Threading

不要依賴atomicity of built-in types, 因為它們可能不是atomic的

使用Queue module的Queue作為thread之間數據溝通方式, 使用threading module以及其locking primitives, 了解條件變量合適的使用方式, 這樣就可以使用threading.Condition來取代低級別的lock了

Atomic

在這邊原文提到了 atomic operations, Atomic是指不能分割的意思, 如果一段程式碼是Atomic, 代表在這段程式碼在執行過程中, 是不能被中斷的

如何使用thread還需要study

Power Features

python是很靈活的語言, 提供了很多花俏的特性, 但可讀性會很差, 避免使用這些特性

Python 風格規範

分號

不要使用分號

行長度

不超過80個字元
可以使用括號, 不要使用反斜槓連接行
在註釋中, 如果必要, 將長的URL放在一行上

Yes

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

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

No

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

括號

寧缺勿濫的使用括號
除非是連接行, 否則不要在返回或條件語句中使用括號

Yes

Yes: if foo:
         bar()
     while x:
         x = bar()
     if x and y:
         bar()
     if not x:
         bar()
     return foo
     for (x, y) in dict.items(): ...

No

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

縮排

用四個空格代替tab縮排, 我猜是因為這樣就沒辦法進行參數對齊

Yes

Yes:   # Aligned with opening delimiter
       foo = long_function_name(var_one, var_two,
                                var_three, var_four)

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

       # 4-space hanging indent; nothing on first line
       foo = long_function_name(
           var_one, var_two, var_three,
           var_four)

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

No

No:    # Stuff on first line forbidden
      foo = long_function_name(var_one, var_two,
          var_three, var_four)

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

空行

頂級定義之間空兩行, 方法定義之間空一行, 函數內覺得合適的地方可以空一行

空格

按照標準的排版規範來使用標點兩邊的空格, 括號內不要有空格

Yes

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

No

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

不要在逗號, 分號, 冒號前面加空格, 但應該在它們後面加(除了在行尾)

Yes

Yes: if x == 4:
         print x, y
     x, y = y, x

No

No:  if x == 4 :
         print x , y
     x , y = y , x

參數列表, 索引或切片的左括號前不應加空格

Yes

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

No

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

在二元操作符兩邊都加上一個空格, 比如賦值(=), 比較(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布爾(and, or, not). 至於算術操作符兩邊的空格該如何使用, 需要你自己好好判斷. 不過兩側務必要保持一致.

Yes

Yes: x == 1

No

No:  x<1

當’=’用於指示關鍵字參數或默認參數值時, 不要在其兩側使用空格

Yes

Yes: def complex(real, imag=0.0): return magic(r=real, i=imag)

No

No:  def complex(real, imag = 0.0): return magic(r = real, i = imag)

不要用空格來垂直對齊多行間的標記, 因為這會成為維護的負擔(適用於:, #, =等):

Yes

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

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

No

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

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

Shebang

大部分.py文件不必以#!作為文件的開始. 根據 PEP-394 , 程序的main文件應該以 #!/usr/bin/python2或者 #!/usr/bin/python3開始.

(譯者注: 在計算機科學中, Shebang (也稱為Hashbang)是一個由井號和歎號構成的字符串行(#!), 其出現在文本文件的第一行的前兩個字符. 在文件中存在Shebang的情況下, 類Unix操作系統的程序載入器會分析Shebang後的內容, 將這些內容作為解釋器指令, 並調用該指令, 並將載有Shebang的文件路徑作為該解釋器的參數. 例如, 以指令#!/bin/sh開頭的文件在執行時會實際調用/bin/sh程序.)

#!先用於幫助內核找到Python解釋器, 但是在導入模塊時, 將會被忽略. 因此只有被直接執行的文件中才有必要加入#!.

註釋

我們對文檔字符串的慣例是使用三重雙引號”“”
一個文檔字符串應該這樣組織: 首先是一行以句號, 問號或驚歎號結尾的概述(或者該文檔字符串單純只有一行). 接著是一個空行. 接著是文檔字符串剩下的部分, 它應該與文檔字符串的第一行的第一個引號對齊. 下面有更多文檔字符串的格式化規範.

每個文件應該包含一個許可樣板. 根據項目使用的許可(例如, Apache 2.0, BSD, LGPL, GPL), 選擇合適的樣板.

函數和方法

一個函數必須要有文檔字符串, 除非它滿足以下條件:

  1. 外部不可見
  2. 非常短小
  3. 簡單明瞭

文檔字符串應該包含函數做什麼, 以及輸入和輸出的詳細描述. 通常, 不應該描述”怎麼做”, 除非是一些複雜的算法. 文檔字符串應該提供足夠的信息, 當別人編寫代碼調用該函數時, 他不需要看代碼, 只要看文檔字符串就可以了
對於複雜的代碼, 在代碼旁邊加註釋會比使用文檔字符串更有意義.
關於函數的幾個方面應該在特定的小節中進行描述記錄, 這幾個方面如下文所述. 每節應該以一個標題行開始. 標題行以冒號結尾. 除標題行外, 節的其他內容應被縮進2個空格.

  • Args:
    列出每個參數的名字, 並在名字後使用一個冒號和一個空格, 分隔對該參數的描述.如果描述太長超過了單行80字符,使用2或者4個空格的懸掛縮進(與文件其他部分保持一致). 描述應該包括所需的類型和含義. 如果一個函數接受foo(可變長度參數列表)或者**bar (任意關鍵字參數), 應該詳細列出`foo**bar`.
  • Returns: (或者 Yields: 用於生成器)
    描述返回值的類型和語義. 如果函數返回None, 這一部分可以省略.
  • 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.
    """
    pass

Class

類應該在其定義下有一個用於描述該類的文檔字符串. 如果你的類有公共屬性(Attributes), 那麼文檔中應該有一個屬性(Attributes)段. 並且應該遵守和函數參數相同的格式.

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

塊註釋和行註釋

最需要寫註釋的是代碼中那些技巧性的部分. 如果你在下次代碼審查的時候必須解釋一下, 那麼你應該現在就給它寫註釋. 對於複雜的操作, 應該在其操作開始前寫上若干行註釋. 對於不是一目瞭然的代碼, 應在其行尾添加註釋.

# 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 iff i is a power of 2

為了提高可讀性, 註釋應該至少離開代碼2個空格.
另一方面, 絕不要描述代碼. 假設閱讀代碼的人比你更懂Python, 他只是不知道你的代碼要做什麼.
只要告訴別人這段代碼在做甚麼, 不用解釋這段代碼怎麼寫的

# BAD COMMENT: Now go through the b array and make sure whenever i occurs
# the next element is i+1

Class

如果一個類不繼承自其它類, 就顯式的從object繼承. 嵌套類也一樣.

Yes

Yes: class SampleClass(object):
         pass


     class OuterClass(object):

         class InnerClass(object):
             pass


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

No

No: class SampleClass:
        pass


    class OuterClass:

        class InnerClass:
            pass

繼承自 object 是為了使屬性(properties)正常工作, 並且這樣可以保護你的代碼, 使其不受Python 3000的一個特殊的潛在不兼容性影響. 這樣做也定義了一些特殊的方法, 這些方法實現了對象的默認語義, 包括 __new__, __init__, __delattr__, __getattribute__, __setattr__, __hash__, __repr__, and __str__ .

Strings

即使參數都是字符串, 使用%操作符或者格式化方法格式化字符串. 不過也不能一概而論, 你需要在+和%之間好好判定.

Yes

Yes: x = a + b
     x = '%s, %s!' % (imperative, expletive)
     x = '{}, {}!'.format(imperative, expletive)
     x = 'name: %s; score: %d' % (name, n)
     x = 'name: {}; score: {}'.format(name, n)

No (看起來如果只是串接在一起, 就使用+, 其他都用format格式)

No: x = '%s%s' % (a, b)  # use + in this case
    x = '{}{}'.format(a, b)  # use + in this case
    x = imperative + ', ' + expletive + '!'
    x = 'name: ' + name + '; score: ' + str(n)

避免在循環中用+和+=操作符來累加字符串. 由於字符串是不可變的, 這樣做會創建不必要的臨時對像, 並且導致二次方而不是線性的運行時間. 作為替代方案, 你可以將每個子串加入列表, 然後在循環結束後用 .join 連接列表. (也可以將每個子串寫入一個 cStringIO.StringIO 緩存中.)

Yes

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

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

在同一個文件中, 保持使用字符串引號的一致性. 使用單引號’或者雙引號”之一用以引用字符串, 並在同一文件中沿用. 在字符串內可以使用另外一種引號, 以避免在字符串中使用. PyLint已經加入了這一檢查.

Yes

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

No

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

為多行字符串使用三重雙引號”“”而非三重單引號’‘’. 當且僅當項目中使用單引號’來引用字符串時, 才可能會使用三重’‘’為非文檔字符串的多行字符串來標識引用. 文檔字符串必須使用三重雙引號”“”. 不過要注意, 通常用隱式行連接更清晰, 因為多行字符串與程序其他部分的縮進方式不一致.

Yes

Yes:
    print ("This is much nicer.\n"
           "Do it this way.\n")

No

No:
      print """This is pretty ugly.
  Don't do this.
  """

文件和sockets

在文件和sockets結束時, 關閉它.

除文件外, sockets或其他類似文件的對象在沒有必要的情況下打開, 會有許多副作用, 例如:

  1. 它們可能會消耗有限的系統資源, 如文件描述符. 如果這些資源在使用後沒有及時歸還系統, 那麼用於處理這些對象的代碼會將資源消耗殆盡.
  2. 持有文件將會阻止對於文件的其他諸如移動、刪除之類的操作.
  3. 僅僅是從邏輯上關閉文件和sockets, 那麼它們仍然可能會被其共享的程序在無意中進行讀或者寫操作. 只有當它們真正被關閉後, 對於它們嘗試進行讀或者寫操作將會跑出異常, 並使得問題快速顯現出來.

而且, 幻想當文件對像析構時, 文件和sockets會自動關閉, 試圖將文件對象的生命週期和文件的狀態綁定在一起的想法, 都是不現實的. 因為有如下原因:

  1. 沒有任何方法可以確保運行環境會真正的執行文件的析構. 不同的Python實現採用不同的內存管理技術, 比如延時垃圾處理機制. 延時垃圾處理機制可能會導致對像生命週期被任意無限制的延長.
  2. 對於文件意外的引用,會導致對於文件的持有時間超出預期(比如對於異常的跟蹤, 包含有全局變量等).

推薦使用 “with”語句 以管理文件:

with open("hello.txt") as hello_file:
    for line in hello_file:
        print line

對於不支持使用”with”語句的類似文件的對象,使用 contextlib.closing():

import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
    for line in front_page:
        print line

TODO註釋

為臨時代碼使用TODO註釋, 它是一種短期解決方案. 不算完美, 但夠好了.

TODO註釋應該在所有開頭處包含”TODO”字符串, 緊跟著是用括號括起來的你的名字, email地址或其它標識符. 然後是一個可選的冒號. 接著必須有一行註釋, 解釋要做什麼. 主要目的是為了有一個統一的TODO格式, 這樣添加註釋的人就可以搜索到(並可以按需提供更多細節). 寫了TODO註釋並不保證寫的人會親自解決問題. 當你寫了一個TODO, 請注上你的名字.

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

如果你的TODO是”將來做某事”的形式, 那麼請確保你包含了一個指定的日期(“2009年11月解決”)或者一個特定的事件(“等到所有的客戶都可以處理XML請求就移除這些代碼”).

導入格式

每個導入應該獨佔一行
導入總應該放在文件頂部, 位於模塊註釋和文檔字符串之後, 模塊全局變量和常量之前. 導入應該按照從最通用到最不通用的順序分組:

  1. 標準庫導入
  2. 第三方庫導入
  3. 應用程序指定導入

並且按照字母順序排序, 忽略大小寫

import foo
from foo import bar
from foo.bar import baz
from foo.bar import Quux
from Foob import ar

語句

通常每個語句應該獨佔一行

不過, 如果測試結果與測試語句在一行放得下, 你也可以將它們放在同一行. 如果是if語句, 只有在沒有else時才能這樣做. 特別地, 絕不要對 try/except 這樣做, 因為try和except不能放在同一行.
Yes

Yes:

  if foo: bar(foo)

No

No:

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

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

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

訪問控制

在Python中, 對於瑣碎又不太重要的訪問函數, 你應該直接使用公有變量來取代它們, 這樣可以避免額外的函數調用開銷. 當添加更多功能時, 你可以用屬性(property)來保持語法的一致性.

另一方面, 如果訪問更複雜, 或者變量的訪問開銷很顯著, 那麼你應該使用像 get_foo() 和 set_foo() 這樣的函數調用. 如果之前的代碼行為允許通過屬性(property)訪問 , 那麼就不要將新的訪問函數與屬性綁定. 這樣, 任何試圖通過老方法訪問變量的代碼就沒法運行, 使用者也就會意識到複雜性發生了變化.

Naming

module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.

  1. 所謂”內部(Internal)”表示僅模塊內可用, 或者, 在類內是保護或私有的.
  2. 用單下劃線(_)開頭表示模塊變量或函數是protected的(使用import * from時不會包含).
  3. 用雙下劃線(__)開頭的實例變量或方法表示類內私有.
  4. 將相關的類和頂級函數放在同一個模塊裡. 不像Java, 沒必要限制一個類一個模塊.
  5. 對類名使用大寫字母開頭的單詞(如CapWords, 即Pascal風格), 但是模塊名應該用小寫加下劃線的方式(如lower_with_under.py). 儘管已經有很多現存的模塊使用類似於CapWords.py這樣的命名, 但現在已經不鼓勵這樣做, 因為如果模塊名碰巧和類名一致, 這會讓人困擾.
Type Public Internal
Modules lower_with_under _lower_with_under
Packages lower_with_under
Classes CapWords _CapWords
Exceptions CapWords
Functions lower_with_under() _lower_with_under()
Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER
Global/Class Variables lower_with_under _lower_with_under
Instance Variables lower_with_under _lower_with_under (protected) or __lower_with_under (private)
Method Names lower_with_under() _lower_with_under() (protected) or __lower_with_under() (private)
Function/Method Parameters lower_with_under
Local Variables lower_with_under

Main

即使是一個打算被用作腳本的文件, 也應該是可導入的. 並且簡單的導入不應該導致這個腳本的主功能(main functionality)被執行, 這是一種副作用. 主功能應該放在一個main()函數中.

在Python中, pydoc以及單元測試要求模塊必須是可導入的. 你的代碼應該在執行主程序前總是檢查 if name == 'main' , 這樣當模塊被導入時主程序就不會被執行.

函數長度

更喜歡小而集中的功能。

我們認識到長函數有時是合適的,因此函數長度沒有硬性限制。如果一個函數超過 40 行左右,考慮是否可以在不破壞程序結構的情況下分解它, 意思是如果函數名稱太長, 通常表示包了太多功能。

類型註解

幫助多人coding時, 能夠更好了解input, output類型
但這段規則很大段, 而且不熟悉, 後續補上


如果喜歡文章, 不妨按下喜歡訂閱支持

如果真的想支持我進行創作與實踐計畫, 也可以進行打賞
BTC (BTC) : 1LFRBqvWR9GGizBzoFddkb5xRpAzKRVoDC
BTC (BSC) : 0xe1cda3eb778d1751af17feac796a4bbe4dbca815
BTC (ERC20) : 0xe1cda3eb778d1751af17feac796a4bbe4dbca815
USDT (TRC20) : TT7wgKuYoHwfRy3vCr38Qy3gnS3JJ1aKvn

如果想使用幣安, 可以使用我的推薦連結可以節省手續費10%
或使用推薦碼 : A5YRMD2T


#Python #google #naming #type #命名規則 #naming rule #coding style







Related Posts

[BE201] 後端中階:ORM 與 Sequelize 改留言板系統

[BE201] 後端中階:ORM 與 Sequelize 改留言板系統

無障礙網頁 HTML屬性

無障礙網頁 HTML屬性

git的操作

git的操作


Comments