1. 程式人生 > >《Python從小白到大牛》第5章 Python編碼規範

《Python從小白到大牛》第5章 Python編碼規範

spec 數列 微信 types optional 的人 斷開 包含 其他人

俗話說:“沒有規矩不成方圓”。編程工作往往都是一個團隊協同進行,因而一致的編碼規範非常有必要,這樣寫成的代碼便於團隊中的其他人員閱讀,也便於編寫者自己以後閱讀。

提示
關於本書的Python編碼規範借鑒了Python官方的PEP8編碼規範^1和谷歌Python編碼規範^2。

命名規範

程序代碼中到處都是標識符,因此取一個一致並且符合規範的名字非常重要。Python中命名規範采用多種不同。不同的代碼元素命名不同,下面分類說明一下。

  • 包名。全部小寫字母,中間可以由點分隔開,不推薦使用下劃線。作為命名空間,包名應該具有唯一性,推薦采用公司或組織域名的倒置,如com.apple.quicktime.v2。

  • 模塊名。全部小寫字母,如果是多個單詞構成,可以用下劃線隔開,如dummy_threading。

  • 類名。采用大駝峰法命名法^3,如SplitViewController。

  • 異常名。異常屬於類,命名同類命名,但應該使用Error作為後綴。如FileNotFoundError。

  • 變量名。全部小寫字母,如果是多個單詞構成,可以用下劃線隔開。如果變量應用於模塊或函數內部,則變量名可以單下劃線開頭;如果變量類內部私有使用變量名可以雙下劃線開頭。不要命名雙下劃線開頭和結尾的變量,這是Python保留的。另外,避免使用小寫L、大寫O和大寫I作為變量名。

  • 函數名和方法名。命名同變量命名。如balance_account、_push_cm_exit。

  • 常量名。全部大寫字母,如果是由多個單詞構成,可以用下劃線隔開,如YEAR和WEEK_OF_MONTH。

命名規範示例如下:

_saltchars = _string.ascii_letters + _string.digits + ‘./‘

def mksalt(method=None):

    if method is None:
        method = methods[0]
    s = ‘${}$‘.format(method.ident) if method.ident else ‘‘
    s += ‘‘.join(_sr.choice(_saltchars) for char in range(method.salt_chars))
    return s

METHOD_SHA256 = _Method(‘SHA256‘, ‘5‘, 16, 63)
METHOD_SHA512 = _Method(‘SHA512‘, ‘6‘, 16, 106)

methods = []
for _method in (METHOD_SHA512, METHOD_SHA256, METHOD_MD5, METHOD_CRYPT):
    _result = crypt(‘‘, _method)
    if _result and len(_result) == _method.total_size:
        methods.append(_method)

註釋規範

Python中註釋的語法有三種:單行註釋、多行註釋和文檔註釋。本節介紹如何規範使用這些註釋。

文件註釋

文件註釋就是在每一個文件開頭添加註釋,采用多行註釋。文件註釋通常包括如下信息:版權信息、文件名、所在模塊、作者信息、歷史版本信息、文件內容和作用等。

下面看一個文件註釋的示例:

# 
# 版權所有 2015 北京智捷東方科技有限公司
# 許可信息查看LICENSE.txt文件
# 描述:
#   實現日期基本功能
# 歷史版本:
#   2015-7-22: 創建 關東升
#   2015-8-20: 添加socket庫
#   2015-8-22: 添加math庫
#

上述註釋只是提供了版權信息、文件內容和歷史版本信息等,文件註釋要根據本身的實際情況包括內容。

文檔註釋

文檔註釋就是文檔字符串,註釋內容能夠生成API幫助文檔,可以使用Python官方提供的pydoc工具從Python源代碼文件中提取信息這些信息,也可以生成HTML文件。所有公有的模塊、函數、類和方法都應該進行文檔註釋。

文檔註釋規範有些“苛刻”。文檔註釋推薦使用一對三重雙引號(""")包裹起來,註意不推薦使用三重單引號(‘‘‘)。文檔註釋應該位於被註釋的模塊、函數、類和方法內部的第一條語句。如果文檔註釋一行能夠註釋完成,結束的三重雙引號也在同一行。如果文檔註釋很長,第一行註釋之後要留一個空行,然後剩下的註釋內容,註釋內容換行是要於開始三重雙引號對齊,最後結束的三重雙引號要獨占一行,並與開始三重雙引號對齊。

下面代碼是Python官方提供的base64.py文件一部分。

#! /usr/bin/env python3

"""Base16, Base32, Base64 (RFC 3548), Base85 and Ascii85 data encodings"""  ①

# Modified 04-Oct-1995 by Jack Jansen to use binascii module
# Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
# Modified 22-May-2007 by Guido van Rossum to use bytes everywhere

import re
import struct
import binascii

bytes_types = (bytes, bytearray)  # Types acceptable as binary data

def _bytes_from_decode_data(s): ②
    if isinstance(s, str):
        try:
            return s.encode(‘ascii‘)
        except UnicodeEncodeError:
            raise ValueError(‘string argument should contain only ASCII characters‘)
    if isinstance(s, bytes_types):
        return s
    try:
        return memoryview(s).tobytes()
    except TypeError:
        raise TypeError("argument should be a bytes-like object or ASCII "
                        "string, not %r" % s.__class__.__name__) from None

# Base64 encoding/decoding uses binascii

def b64encode(s, altchars=None):
    """Encode the bytes-like object s using Base64 and return a bytes object.   ③

    Optional altchars should be a byte string of length 2 which specifies an    ④
    alternative alphabet for the ‘+‘ and ‘/‘ characters.  This allows an
    application to e.g. generate url or filesystem safe Base64 strings.
    """ ⑤
    encoded = binascii.b2a_base64(s, newline=False)
    if altchars is not None:
        assert len(altchars) == 2, repr(altchars)
        return encoded.translate(bytes.maketrans(b‘+/‘, altchars))
return encoded

上述代碼第①行是只有一行的文檔註釋,代碼第③行\~第⑤行是多行的文檔註釋,註意它的第一行後面是一個空行,代碼第④行接著進行註釋,它要與開始三重雙引號對齊。代碼第⑤行是結束三重雙引號,它獨占一行的,而且與對齊開始三重雙引號對齊。另外,代碼第②行定義的函數沒有文檔註釋,這是因為該函數是模塊私有的,通過它的命名_bytes_from_decode_data可知它是私有的,下劃線開頭函數是模塊私有的。

代碼註釋

程序代碼中處理文檔註釋還需要在一些關鍵的地方添加代碼註釋,文檔註釋一般是給一些看不到源代碼的人看的幫助文檔,而代碼註釋是給閱讀源代碼的人參考的。代碼註釋一般是采用單行註釋和多行註釋。

示例代碼如下:

# Base32 encoding/decoding must be done in Python     ①
_b32alphabet = b‘ABCDEFGHIJKLMNOPQRSTUVWXYZ234567‘
_b32tab2 = None
_b32rev = None

def b32encode(s):
    """Encode the bytes-like object s using Base32 and return a bytes object.
    """
    global _b32tab2
    # Delay the initialization of the table to not waste memory     ②
    # if the function is never called     ③
    if _b32tab2 is None:
        b32tab = [bytes((i,)) for i in _b32alphabet]
        _b32tab2 = [a + b for a in b32tab for b in b32tab]
        b32tab = None

    if not isinstance(s, bytes_types):
        s = memoryview(s).tobytes()
    leftover = len(s) % 5
    # Pad the last quantum with zero bits if necessary     ④
    if leftover:
        s = s + b‘\0‘ * (5 - leftover)  # Don‘t use += !
    encoded = bytearray()
    from_bytes = int.from_bytes
    b32tab2 = _b32tab2
    for i in range(0, len(s), 5):
        c = from_bytes(s[i: i + 5], ‘big‘)
        encoded += (b32tab2[c >> 30] +           # bits 1 – 10     ⑤
                    b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
                    b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
                    b32tab2[c & 0x3ff]           # bits 31 - 40
                   )
    # Adjust for any leftover partial quanta
    if leftover == 1:
        encoded[-6:] = b‘======‘
    elif leftover == 2:
        encoded[-4:] = b‘====‘
    elif leftover == 3:
        encoded[-3:] = b‘===‘
    elif leftover == 4:
        encoded[-1:] = b‘=‘
    return bytes(encoded)

上述代碼第②行\~第④行都是單行註釋,要求與其後的代碼具有一樣的縮進級別。代碼第①行\~第③行是多行註釋,註釋時要求與其後的代碼具有一樣的縮進級別。代碼第⑤行是尾端進行註釋,這要求註釋內容極短,應該再有足夠的空白(至少兩個空格)來分開代碼和註釋。

使用TODO註釋

PyCharm等IDE工具都為源代碼提供了一些特殊的註釋,就是在代碼中加一些標識,便於IDE工具快速定位代碼,TODO註釋就是其中的一種。TODO註釋雖然不是Python官方所提供的,但是主流的IDE工具也都支持TODO註釋。TODO註釋說明此處有待處理的任務,或代碼沒有編寫完成。

示例代碼如下:

import com.pkg2.hello as module1
from com.pkg2.hello import z

y = 20

# TODO 聲明函數 

print(y)  # 訪問當前模塊變量y
print(module1.y)  # 訪問com.pkg2.hello模塊變量y
print(z)  # 訪問com.pkg2.hello模塊變量z

這些註釋在PyCharm工具的TODO視圖查看,如果沒有打開TODO視圖,可以將鼠標放到PyCharm左下角

技術分享圖片

按鈕上,如圖5-1所示彈出的菜單,選擇TODO,打開如圖5-2所示的TODO視圖,單擊其中的TODO可跳轉到註釋處。

技術分享圖片

技術分享圖片

導入規範

導入語句總是放在文件頂部,位於模塊註釋和文檔註釋之後,模塊全局變量和常量之前。每一個導入語句只能導入一個模塊,示例代碼如下。

推薦:

import re
import struct
import binascii

不推薦:

import re, struct, binascii

但是如何from import後面可以多個代碼元素。

from codeop import CommandCompiler, compile_command

導入語句應該按照從通用到特殊的順序分組,順序是:標準庫→第三方庫→自己模塊,每一組之間有一個空行,而且組中模塊是按照英文字典順序排序的。

import io       ①
import os
import pkgutil
import platform
import re
import sys
import time ②

from html import unescape       ③

from com.pkg1 import example    ④

上述代碼中導入語句分為三組,代碼第①行\~第②行是標準庫中的模塊,註意它的導入順序是有序的。代碼第③行是導入第三方庫中的模塊,代碼第④行是導入自己的模塊。

代碼排版

代碼排版包括空行、空格、斷行和縮進等內容。代碼排版內容比較多,工作量很大,也非常重要。

空行

空行用以將邏輯相關的代碼段分隔開,以提高可讀性。下面是使用空行的規範。

  1. import語句塊前後保留兩個空行,示例代碼如下,其中①②處和③④處是兩個空行。
    
    # Copyright 2007 Google, Inc. All Rights Reserved.
    # Licensed to PSF under a Contributor Agreement.

"""Abstract Base Classes (ABCs) according to PEP 3119."""


from _weakrefset import WeakSet

2. 函數聲明之前保留兩個空行,示例代碼如下,其中①②處處是兩個空行。
```python
from _weakrefset import WeakSet 
①
②
def abstractmethod(funcobj):
    funcobj.__isabstractmethod__ = True
    return funcobj
  1. 類聲明之前保留兩個空行,示例代碼如下,其中①②處是兩個空行。

    ①
    ②
    class abstractclassmethod(classmethod):
    __isabstractmethod__ = True
    
    def __init__(self, callable):
        callable.__isabstractmethod__ = True
        super().__init__(callable)
    1. 方法聲明之前保留一個空行,示例代碼如下,其中①處是一個空行。
      class abstractclassmethod(classmethod):
      __isabstractmethod__ = True
      ①
      def __init__(self, callable):
      callable.__isabstractmethod__ = True
      super().__init__(callable)
    2. 兩個邏輯代碼塊之間應該保留一個空行,示例代碼如下,其中①處是一個空行。
      def convert_timestamp(val):
      datepart, timepart = val.split(b" ")
      year, month, day = map(int, datepart.split(b"-"))
      timepart_full = timepart.split(b".")
      hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
      if len(timepart_full) == 2:
          microseconds = int(‘{:0<6.6}‘.format(timepart_full[1].decode()))
      else:
          microseconds = 0
      ①
      val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
      return val

      空格

代碼中的有些位置是需要有空格的,這個工作量也很大。下面是使用空格的規範。

  1. 賦值符號“=”前後各有一個空格。
    a = 10
    c = 10
  2. 所有的二元運算符都應該使用空格與操作數分開。
    a += c + d
  3. 一元運算符:算法運算符取反“-”和運算符取反“\~”。
    b = 10
    a = -b
    y = \~b
  4. 括號內不要有空格,Python中括號包括:小括號“()”、中括號“[]”和大括號“{}”。

推薦:

doque(cat[1], {dogs: 2}, [])

不推薦:

doque(cat[ 1 ], { dogs: 2 }, [ ])

5.
不要在逗號、分號、冒號前面有空格,而是要在他們後面有一個空格,除非該符號已經是行尾了。

推薦:

if x == 88:
  print x, y
x, y = y, x

不推薦:

if x == 88 :
  print x , y
x , y = y , x
  1. 參數列表、索引或切片的左括號前不應有空格。

推薦:

doque(1)
dogs[‘key‘] = list[index]

不推薦:

doque (1)
dict [‘key‘] = list [index]

縮進

4個空格常被作為縮進排版的一個級別。雖然在開發時程序員可以使用制表符進行縮進,而默認情況下一個制表符等於8個空格,但是不同的IDE工具中一個制表符與空格對應個數會有不同,所以不要使用制表符縮進。

代碼塊的內容相對於首行縮進一個級別(4個空格),示例如下:

class abstractclassmethod(classmethod):
    __isabstractmethod__ = True

    def __init__(self, callable):
        callable.__isabstractmethod__ = True
        super().__init__(callable)

def __new__(mcls, name, bases, namespace, **kwargs):
    cls = super().__new__(mcls, name, bases, namespace, **kwargs)
    for base in bases:
        for name in getattr(base, "__abstractmethods__", set()):
            value = getattr(cls, name, None)
            if getattr(value, "__isabstractmethod__", False):
                abstracts.add(name)
    cls.__abstractmethods__ = frozenset(abstracts)

    return cls

斷行

一行代碼中最多79個字符。對於文檔註釋和多行註釋時一行最多72個字符,但是如何註釋中包含URL地址可以不受這個限制。否則,如果超過則需斷行,可以依據下面的一般規範斷開:

  1. 在逗號後面斷開。
    bar = long_function_name(name1, name2,
                         name3, name4)
    def long_function_name(var_one, var_two,
                       var_three, var_four):
  2. 在運算符前面斷開。
    name1 = name2 * (name3 + name4
                         - name5) + 4 * name6
  3. 盡量不要使用續行符(\),當有括號(包括:大括號、中括號和小括號)則在括號中斷開,這樣可以不使用續行符。

    
    def long_function_name(var_one, var_two,
                       var_three, var_four):
    return var_one + var_two + var_three \  ①
           + var_four   

name1 = name2 * (name3 + name4

  • name5) + 4 * name6

bar = long_function_name(name1, name2,
name3, name4)

foo = {
long_dictionary_key: name1 + name2

  • name3 + name4 - name5
    }

c = list[name2 * name3

  • name4 - name5 + 4 * name6]
    上述代碼第①行使用了續航符進行斷行,其他的斷行都是在括號中實現的,所以省略了續行符。有時為了省略續行符,會將表達式用小括號包裹起來,如下代碼所示:
    ```python
    def long_function_name(var_one, var_two,
                       var_three, var_four):
    return (var_one + var_two + var_three 
           + var_four)

    提示
    在Python中反斜杠(\)可以作為續行符使用,告訴解釋器當前行和下一行是連接在一起的。但如果在大括號、中括號和小括號中續行是隱式的。

本章小結

通過對本章內容的學習,讀者可以了解到Python編碼規範,包括命名規範、註釋規範、導入規範和代碼排版等內容。

配套視頻

http://edu.51cto.com/topic/1507.html

配套源代碼

http://www.zhijieketang.com/group/8

電子書

https://yuedu.baidu.com/ebook/5823871e59fafab069dc5022aaea998fcc2240fc

作者微博:@tony_關東升郵箱:[email protected]
br/>郵箱:[email protected]
Python讀者服務QQ群:628808216

《Python從小白到大牛》第5章 Python編碼規範