Python編碼規范

Python編碼規范

2018.6


1. 縮進

  • (垂直隱式縮進)對準左括號。
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
  • (懸掛縮進) 一般情況只需多一層縮進。
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
  • (懸掛縮進) 但下面情況, 需再加多一層縮進, 和后續的語句塊區分開來。
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)
  • 多行結構中的大括號、中括號、小括號的右括號可與內容對齊單獨起一行作為最后一行的第一個字符。
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]

result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
    )
  • 也可以與多行結構的第一行第一個字符對齊。
my_list = [
    1, 2, 3,
    4, 5, 6,
]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)
  • 當if語句的條件判斷部分長到需要換行寫的時候,注意可以在if后面增加一個空格,再增加一個左括號來創造一個4空格縮進的多行條件。如下是if語句推薦使用的三種縮進形式。
# 沒有額外的縮進,但條件判斷的語句會和后面的語句產生視覺沖突
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# 增加一個注釋,在能提供語法高亮的編輯器中可以有一些區分
if (this_is_one_thing and
    that_is_another_thing):
    # 這里添加注釋,區分前后兩部分的語句
    do_something()

# 在條件判斷的語句添加額外的縮進,和后面語句區分
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

?


2. 換行

  • 每行代碼控制在120個字符以內(文檔/注釋限制在72個字符以內)。優先選擇在小括號、中括號以及大括號中的隱式續行方式,比如通過小括號內表達式的換行方式將長串折成多行,如下所示。
query_str = ("select DriveLetter " 
             "from Win32_Volume " 
             "where Label = \"%s\"") % (args.logusn)
  • 如果如無法使用隱式續行,則使用反斜杠 \ 續行,如下所示。
with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())
  • 公式應該在二元運算符之前中斷換行,即換行之后的續行一開始是二元運算符,這樣運算符和操作數容易進行匹配,如下所示。
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)
  • 不要使用復合語句,即同一行中存在多個語句,錯誤如下所示。
if foo == 'something': do_blah_thing()
do_one(); do_two(); do_three()

?


3. 空行

  • 模塊級函數和類定義之間空兩行,類成員函數之間空一行,在函數中使用空行來區分邏輯段(謹慎使用)。
class Common(object):
    """common class,提供通用的方法"""
    def __init__(self):
        """common的構造器:"""
        pass

    def method1(self):
        pass

    def method2(self):
        pass


def method3():
    pass

?


4. Imports導入

  • 各個import應該獨立成行。
# 正確
import os
import sys

# 錯誤
import os, sys
  • import總是位于文件的頂部,在模塊注釋和文檔字符串之后,在模塊的全局變量與常量之前。

  • 應按照以下順序import,且各import類型之間需要用空行隔開。

    1. 標準庫
    2. 第三方庫
    3. 本地庫
  • 應避免使用通配符進行import,即from xxx import *,建議使用如下形式。

from subprocess import Popen, PIPE
  • 像__all__ , __author__ , __version__ 等這樣的模塊級屬性,應該放在文檔字符串的后面,以及除from
    __future__ 之外的import表達式前面。
"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'John'

import os
import sys
  • 包(package)內的模塊導入:
    包是一個含有多個模塊文件的文件夾,Python2需要在這個文件內添加一個__init__.py文件,該文件內容可以為空,Python3可加可不加__init__.py,但為了規范最好加上。包內的模塊文件不能作為頂層模塊來執行,即不能作為主函數的入口。
    包內的模塊文件避免使用隱式相對導入同目錄下的其它模塊(Python3禁止使用)。建議在包內使用相對導入,在包外使用絕對導入。
package
├── __init__.py
├── sub_pkg1
│   ├── __init__.py
│   ├── module_x.py
│   ├── module_y.py
└── sub_pkg2
    ├── __init__.py
    └── module_z.py

# module_x.py
import module_y  # 隱式相對導入
from module_y import var  # 隱式相對導入
from . import module_y  # 顯式相對導入
from .module_y import var  # 顯式相對導入
from sub_pkg1 import module_y  # 絕對導入

?


5. 表達式和語句中的空格

  • 如下二元運算符兩邊要放置一個空格:

    1. 賦值( = )
    2. 增量賦值( += , -=等 )
    3. 比較運算( == , < , > , != , <> , <= , >= , in , not in , is , is not )
    4. 布爾運算( and , or , not )
  • 如果使用具有不同優先級的運算符,請考慮在具有最低優先級的運算符周圍添加空格。有時需要通過自己來判斷,但是不要使用一個以上的空格,并且在二元運算符的兩邊使用相同數量的空格。

# 正確寫法
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)

# 錯誤寫法
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
  • 函數參數列表中,逗號之后要有空格,但在制定關鍵字參數或者默認參數值的時候,不要在 = 附近加上空格。
# 正確寫法
def complex(real, imag=0.0):
    return magic(r=real, i=imag)

# 錯誤寫法
def complex(real,imag = 0.0):
    return magic(r = real, i = imag)
  • 其他注意事項:

    • 一行的尾部不要有空格。
    • 緊接著圓括號、方括號和花括號的內側不加空格。
    # 正確寫法
    spam(ham[1], {eggs: 2})
    
    # 錯誤寫法
    spam( ham[ 1 ], { eggs: 2 } )
    
    • 逗號及后面的括號之間不加空格。
    # 正確寫法
    foo = (0,)
    
    # 錯誤寫法
    bar = (0, )
    
    • 逗號,分號或冒號之前不加空格。
    # 正確寫法
    if x == 4: print x, y; x, y = y, x
    
    # 錯誤寫法
    if x == 4 : print x , y ; x , y = y , x
    
    • 在一個切片中,冒號就像一個二元運算符(把它當作優先級最低的運算符),在冒號兩邊應該有相等的空格數量,但當一個切片參數被省略時,空格也被省略了。
    # 正確寫法
    ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
    ham[lower:upper], ham[lower:upper:], ham[lower::step]
    ham[lower+offset : upper+offset]
    ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
    ham[lower + offset : upper + offset]
    
    # 錯誤寫法
    ham[lower + offset:upper + offset]
    ham[1: 9], ham[1 :9], ham[1:9 :3]
    ham[lower : : upper]
    ham[ : upper]
    
    • 函數調用參數列表的圓括號前不加空格。
    # 正確寫法
    spam(1)
    
    # 錯誤寫法
    spam (1)
    
    • 索引或切片的方括號前不加空格。
    # 正確寫法
    dct['key'] = lst[index]
    
    # 錯誤寫法
    dct ['key'] = lst [index]
    
    • 在賦值語句的運算符周圍,不要為了對齊而使用多個空格。
    # 正確寫法
    x = 1
    y = 2
    long_variable = 3
    
    # 錯誤寫法
    x             = 1
    y             = 2
    long_variable = 3
    

?


6. 注釋

  • 塊注釋
    塊注釋通常在代碼前,并和代碼有同樣的縮進。每行以#開頭, 而且#后面有單個空格。塊注釋內部的段落通過只有一個#的空行分隔。
# 塊注釋段落1
#
# 塊注釋段落2
#
# 塊注釋段落3

下面是一個py文件開頭塊注釋的格式。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# title           :backup_full.py
# description     :MySQL全量備份
# author          :張雪峰
# date            :20180601
# version         :1.0
# notes           :
# python_version  :3.6
#==============================================================================
  • 行內注釋
    #前至少使用兩個空格和代碼語句分開,#后面有單個空格,注意避免使用無意義的注釋。
x = x + 1                 # 行內注釋
  • 文檔說明
    要為所有的公共模塊,函數,類以及方法編寫文檔說明。

    • 多行的文檔說明,使用的結尾三引號應該自成一行。
    """This is the example module.
    
    This module does stuff.
    """
    
    • 單行的文檔說明,尾部的三引號應該和文檔在同一行。
    def mount(args):
        '''mount to new mysql instance'''
        target_dir = args.target_dir
    
    • 公共方法的文檔說明要注明入參和返回值。
    def backup_alone(self, args):  
        """單通道備份函數
        Parameters:
            filesystem(str): 文件系統
            mountpoint(str): 掛載點
            include(str): 包含文件
            exclude(str): 排除文件
        Returns:
            pid(str): 進程號   
        """
    
    • 非公共的方法沒有必要寫文檔說明,但是應該有一個描述方法具體作用的注釋,這個注釋應該在def那一行之后。
    def backup_synth(args):
        # 增量備份
        logger.info('backup_synth')
    
    

?


7. 命名

  • 模塊和包
    模塊名應該簡短,全部用小寫字母,多字母之間可以使用單下劃線連接。
    包(通常是包含__init__.py及其他模塊文件的目錄文件)名也應該使用簡短全小寫的名字,但不建議用下劃線。
package
├── __init__.py
├── sub_pkg1
│   ├── __init__.py
│   ├── module_x.py
│   ├── module_y.py
└── sub_pkg2
    ├── __init__.py
    └── module_z.py

  • 類名使用駝峰命名風格,首字母大寫,私有類用_開頭。
class BackupFile(object):
    """Backup_File class"""
  • 異常
    異常也是類,所以也使類名規則,但是應該增加后綴Error。
class SomeCustomError(Exception):
    def __init__(self,str_length):
        super(SomeCustomError,self).__init__()
        self.str_length = str_length
        
try:
    s = raw_input("輸入一行字符串:\n")
    # 輸入字符串長度超過指定長度范圍,引發異常
    if (length < len(s)):
        raise SomeCustomError(length)
except SomeCustomError as x:
    print("捕獲自定義異常")
  • 全局變量
    全局變量名應盡量只在模塊內部使用, 對可能使用語句 from moduleName import variableName 而被導入的模塊,應采用 __all__ 機制來防止全局變量被別的模塊導入, 或者在全局變量名開頭加一個前置下劃線。
__all__ = ['var1', 'var2', 'var3']
_name = 'var4'
  • 函數和變量
    函數和變量名一律小寫,如果想提高可讀性可以用下劃線分隔。
status_code = 0

def thread_fun(self, cmdstr):
    """單個線程"""
  • 方法參數
    類的方法第一個參數必須是self,而類方法第一個參數必須是cls。
class Kls(object):
    no_inst = 0
    def __init__(self):
        Kls.no_inst = Kls.no_inst + 1

    @classmethod
    def get_no_of_instance(cls):
        return cls.no_inst
  • 常量
    常量命名使用全部大寫的方式,可以使用下劃線。
HOST = "192.168.1.10"
PORT = 7777
NUM_LOG = 100000

?


8. 異常

  • 自定義異常類應從Exception繼承,而不是BaseException。
class SomeCustomError(Exception):
    def __init__(self,str_length):
        super(SomeCustomError,self).__init__()
        self.str_length = str_length
  • 當給捕捉的異常綁定一個名字時,使用在Python2.6中加入的as的語法。
try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))
  • except后面需要指定捕捉的異常,裸露的except會捕捉所有異常,意味著會隱藏潛在的問題,可以有多個except語句,捕捉多種異常,分別做異常處理。
# 正確寫法
try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None
    
# 錯誤寫法 
try:
    import platform_specific_module
except:
    platform_specific_module = None
  • try/except子句中的語句不要太多,只在可能發生異常的地方使用,以免屏蔽掉其他的錯誤:
# 正確寫法
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

# 錯誤寫法 
try:
    return handle_value(collection[key])
except KeyError:
    # 可能會捕獲handle_value()引發的KeyError,而不是collection的 
    return key_not_found(key)

?


9. 接收和返回

  • 接收

    • 短選項:一個字符表示(區分大小寫),以 - 為前綴。后面有 : 的字符表示該選項需要參數。傳遞參數的時候,要用 - 作為前綴,加上相應選項字符,該選項如果有參數的話,直接或空格后跟在后面。如 -h, 即命令行選項為 h, 如 -p80 或 -p 80, 即命令行選項為 p, 參數為 80。
    • 長選項:一個字符串表示,以 -- 為前綴。后面有 = 的字符串表示該選項需要參數。傳遞參數的時候,要用 -- 作為前綴,加上相應選項字符串,該選項如果有參數的話,用 = 或空格加上參數跟在后面。如 --help, 即命令行選項為 help, 如 --port=80 或 --port 80, 即命令行選項為 port, 參數為 80。
  • 返回

    • 返回普通請求
    # {"code":"錯誤碼/正確為0", "msg":"具體錯誤/成功信息"}
    {"code":"0", "msg":"New storage successfully"}
    {"code":"1001", "msg":"The storage offline"}
    
    • 返回展示數據請求
    # {"code":"錯誤碼/正確為0", "msg":"具體錯誤/成功信息", "data":[{"具體展示的數據1", "具體展示的數據2"}]}
    {"code":"0", "msg":"New storage successfully", "data":[{"storage1", "storage2"}]}
    

?


10. 其他

  • Linux下運行的腳本開頭要添加注釋申明Python解釋器的位置,推薦使用 #!/usr/bin/env python,而不是 #!/usr/bin/python。

  • 默認使用utf-8編碼規則,在文件頭部(Python解釋器位置聲明之后)添加 #-*-coding:utf-8-*- 標識。

  • 和像None這樣的單例對象進行比較的時候應該始終用 is 或者 is not,永遠不要用 == 運算符。

  • 用 is not 代替 not … is,前者的可讀性更好。

  • 如果一個資源是一段代碼本地使用的,應使用with語句保證這個資源使用完后被正確釋放, try...finally 語句也可以。

with open("/document/foo.txt") as f:
    data = f.read()
    
try:   
    f = open("/document/foo.txt", "r")   
except IOError as e:
    print(e)
finally:
    f.close()
  • 函數或者方法在沒有返回值時要明確返回None。
# 正確寫法
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

# 錯誤寫法
def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)
  • 使用 .startswith() 和 .endswith() 而非字符切片去檢測前綴或后綴。
# 推薦寫法       
if foo.startswith('bar'):
    pass

# 不推薦寫法
if foo[:3] == 'bar':
    pass
  • 使用 isinstance() 代替對象類型的比較。
# 推薦寫法  
if isinstance(obj, int):
    pass

# 不推薦寫法  
if type(obj) is type(1):
    pass
  • 不要用 == 和布爾值進行比較。
# 推薦寫法   
if greeting:
    pass

# 不推薦寫法
if greeting == True:
    pass

# 不推薦寫法
if greeting is True:
    pass
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容