Python - google代碼規(guī)范學(xué)習(xí)筆記
此編碼風(fēng)格指南主要基于 Google Python Style Guide [中譯版],結(jié)合百度python使用習(xí)慣和實(shí)際開發(fā)情況制定。
1. 語言規(guī)范
1.1 import
[強(qiáng)制] 禁止使用from xxx import yyy語法直接導(dǎo)入類或函數(shù)(即yyy只能是module或package,不能是類或函數(shù))。
[強(qiáng)制] 禁止使用from xxx import *
[強(qiáng)制] import時必須使用package全路徑名(相對PYTHONPATH),禁止使用相對路徑(相對當(dāng)前路徑)。
1.2 異常
[強(qiáng)制] 除非重新拋出異常,禁止使用except:捕獲所有異常。
[強(qiáng)制] 捕捉異常時,應(yīng)當(dāng)使用as語法,禁止使用逗號語法。
[強(qiáng)制] 禁止使用雙參數(shù)形式(raise MyException, 'Error Message')或字符串形式(raise 'Error Message')語法拋異常。
# 正確 try: …… except Exception as e: raise MyException('Error Message!') # 錯誤 raise MyException, 'Error Message'raise 'Error Message'
[強(qiáng)制] 如果需要自定義異常,應(yīng)該在模塊內(nèi)定義名為Error的異常基類。該基類必須繼承自Exception。其它異常類都從該Error類派生而來。
class Error(Exception): def __init__(self,err='XXX錯誤'): Exception.__init__(self,err) class DatabaseException(Error): def __init__(self,err='數(shù)據(jù)庫錯誤'): DatabaseException.__init__(self,err)
====
[建議] 可以使用異常。但使用前請務(wù)必詳細(xì)了解異常的行為,謹(jǐn)慎使用。
[建議] 除非重新拋出異常,否則不建議捕獲Exception或StandardError。如果捕獲,必須在日志中記錄所捕獲異常信息。
[建議] 建議try中的代碼盡可能少。避免catch住未預(yù)期的異常,掩藏掉真正的錯誤。
[建議] 建議使用finally子句來執(zhí)行那些無論try塊中有沒有異常都應(yīng)該被執(zhí)行的代碼,這對于清理資源常常很有用。例如:文件關(guān)閉。
1.3 全局變量
[強(qiáng)制] 禁止使用全局變量。除了以下例外:
- 腳本默認(rèn)參數(shù)
- 模塊級常量
[強(qiáng)制] 如果定義全局變量,必須寫在文件頭部。
1.4 構(gòu)造函數(shù)
[建議] 類構(gòu)造函數(shù)應(yīng)該盡量簡單,不能包含可能失敗或過于復(fù)雜的操作。
1.5 函數(shù)返回值
·[強(qiáng)制] 函數(shù)返回值必須小于等于3個。
3個以上時必須通過class/namedtuple/dict等具名形式進(jìn)行包裝。
## Ex1 def get_numbers(): return 1, 2, 3 a, b, c = get_numbers() ## Ex2 class class Person(object): def __init__(self, name, gender, age, weight): self.name = name self.gender = gender self.age = age self.weight = weight ## Ex3 namedtuple import collections Person = collections.namedtuple('Person', 'name gender age weight') ## Ex4 dict def get_person_info(): return Person('jjp', 'MALE', 30, 130) person = get_person_info()
1.6 嵌套/局部/內(nèi)部類或函數(shù)
[建議] 不推薦使用嵌套/局部/內(nèi)部類或函數(shù)。
1.7 列表推導(dǎo)
[強(qiáng)制] 可以使用列表推導(dǎo)。mapping、loop、filter部分單獨(dú)成行,且最多只能寫一行。禁止多層loop或filter。
(解釋:復(fù)雜的列表推導(dǎo)難以理解,建議轉(zhuǎn)換成對應(yīng)的for循環(huán))
1.8 默認(rèn)迭代器和操作符
·[強(qiáng)制] 對容器或文件的只讀遍歷,應(yīng)該使用內(nèi)置的迭代方法,不要使用返回list的方式遍歷。
·[強(qiáng)制] [PY013] 對容器類型,使用in或not in判斷元素是否存在。而不是has_key。
# ============ YES ============ for key in adict: .... if key not in adict: .... if obj in alist: .... for line in afile: .... for k, v in dict.iteritems(): .... # 刪除畢業(yè)學(xué)生 for id in students.keys(): if students[id].graduated: del students[id] # ============ No ============ for key in adict.keys(): .... if not adict.has_key(key): .... for line in afile.readlines(): .... #刪除畢業(yè)學(xué)生 for id in students: if students[id].graduated: del students[id] # 拋出RuntimeError異常
1.9 生成器
·[建議] 當(dāng)返回較長列表數(shù)據(jù)時建議使用yield和generator函數(shù)。
#返回n以內(nèi)的奇數(shù) def odds(n): for i in xrange(1, n + 1): if i % 2 == 1: yield i for i in odds(1000): print i # ============================ def odds(n): ret = [] for i in xrange(1, n + 1): if i % 2 == 1: ret.append(i) return ret for i in odds(1000): print i
1.10 lambda函數(shù) 
·[強(qiáng)制] 可以使用lambda函數(shù),但僅限一行之內(nèi)。
1.11 條件表達(dá)式
[強(qiáng)制] 條件表達(dá)式僅用于一行之內(nèi),禁止嵌套使用
1.12 默認(rèn)參數(shù)
[強(qiáng)制] 僅可使用以下基本類型字面常量或常量作為默認(rèn)參數(shù):整數(shù)、bool、浮點(diǎn)、字符串、None
(解釋:以可修改的對象(如list、dict、object等)作為默認(rèn)參數(shù),可能會被不小心改掉,導(dǎo)致默認(rèn)值發(fā)生變化,產(chǎn)生難以追查的錯誤)
def foo(a, b=None): if b is None: b = []
1.13 屬性(properties)
property() 函數(shù)的作用是在新式類中返回屬性值。
[強(qiáng)制] 可以使用property。但禁止在派生類里改寫property實(shí)現(xiàn)。
(解釋:由于property是在基類中定義的,默認(rèn)綁定到基類的實(shí)現(xiàn)函數(shù)。
若允許在派生類中改寫property實(shí)現(xiàn),則需要在基類中通過間接方式調(diào)用property實(shí)現(xiàn)函數(shù)。
這個方法技巧性太強(qiáng),可讀性差,所以禁止使用。)
import math 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 def __get_area(self): '''Calculates the 'area' property.''' return self.side ** 2 def __set_area(self, area): '''Sets the 'area' property.''' self.side = math.sqrt(area) def __del_area(self, area): '''Del the 'area' property.''' del self.side area = property(__get_area, __set_area, doc="""Gets or sets the area of the square.""") @property def perimeter(self): return self.side * 4
1.14 True/False求值
[強(qiáng)制] 禁止使用==或!=判斷表達(dá)式是否為None,應(yīng)該用is或is not None
[強(qiáng)制] 當(dāng)明確expr為bool類型時,禁止使用==或!=與True/False比較。應(yīng)該替換為expr或not expr
[強(qiáng)制] 判斷某個整數(shù)表達(dá)式expr是否為零時,禁止使用not expr,應(yīng)該使用expr == 0
[建議] 建議顯式轉(zhuǎn)換到bool類型,慎用到bool類型的隱式轉(zhuǎn)換。如使用隱式轉(zhuǎn)換,你需要確保充分了解其語義
if users is None or len(users) == 0: print 'no users'
foo = True if not foo: self.handle_zero() if i % 10 == 0: self.handle_multiple_of_ten()
2. 風(fēng)格規(guī)范
2.1 分號
[強(qiáng)制] 禁止以分號結(jié)束語句
[強(qiáng)制] 一行只能寫一條語句,沒有例外情況
2.2 行列長度
[強(qiáng)制] 每行不得超過100個字符
[強(qiáng)制] 函數(shù)長度不得超過100行
2.3 縮進(jìn)
·[強(qiáng)制] 使用4個空格縮進(jìn),禁止使用tab縮進(jìn)。
·[強(qiáng)制] 把單行內(nèi)容拆成多行寫時,要么與首行保持對齊;要么首行留空,從第二行起統(tǒng)一縮進(jìn)4個空格;為與后面的代碼區(qū)分,可以使用8空格縮進(jìn)。
(解釋:不同編輯器對TAB的設(shè)定可能不同,使用TAB容易造成在一些編輯器下代碼混亂,所以建議一率轉(zhuǎn)換成空格。)
# Aligned with opening delimiter 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, ... }
2.4 空行
[強(qiáng)制] 文件級定義(類或全局函數(shù))之間隔兩個空行,類方法之間隔一個空行
2.5 空格
[強(qiáng)制] 圓括號、方括號、花括號內(nèi)側(cè)都不加空格
spam(ham[1], {eggs: 2}, [])
[強(qiáng)制] 參數(shù)列表, 索引或切片的左括號前不應(yīng)加空格
dict['key'] = list[index]
[強(qiáng)制] 逗號、分號、冒號前不加空格,后邊加一個空格
if x == 4:
print x, y
x, y = y, x
[強(qiáng)制] 所有二元運(yùn)算符前后各加一個空格
x == 1
[強(qiáng)制] 關(guān)鍵字參數(shù)或參數(shù)默認(rèn)值里的等號前后不加空格
def complex(real, imag=0.0): return magic(r=real, i=imag)
2.6 注釋
[強(qiáng)制] 使用文檔字符串(docstring)描述module、function、class和method接口。docstring必須用三個雙引號括起來。
[強(qiáng)制] 對外接口部分必須用docstring描述,內(nèi)部接口視情況自行決定是否寫docstring。
[強(qiáng)制] 接口的docstring描述至少包括功能簡介、參數(shù)、返回值。如果可能拋出異常,必須注明。
[強(qiáng)制] 每個文件都必須有文件聲明,文件聲明必須包括以下信息:版權(quán)聲明,功能和用途簡介,修改人及聯(lián)系方式。
"""[parse error message] Args: e (error): [error] Returns: str: [parsed error message] Raises: IOError: An error occurred accessing the bigtable.Table object. """
2.7 import格式
[強(qiáng)制] 每行只能導(dǎo)入一個庫
- [強(qiáng)制] 必須按如下順序排列
import
- ,每部分之間留一個空行
- 標(biāo)準(zhǔn)庫
- 第三方庫
- 應(yīng)用程序自有庫
import os import sys from third.party import lib from third.party import foobar as fb import my.own.module
2.8 命名規(guī)則
[強(qiáng)制] 類(包括異常)名使用首字母大寫駝峰式命名
[強(qiáng)制] 常量使用全大寫字母,單詞間用下劃線分隔
[強(qiáng)制] 其它情況(目錄/文件/package/module/function/method/variable/parameter)一律使用全小寫字母,單詞間用下劃線分隔
[強(qiáng)制] protected成員使用單下劃線前綴,private成員使用雙下劃線前綴
[強(qiáng)制] 禁止使用雙下劃線開頭,雙下劃線結(jié)尾的名字(類似__init__)
3. 編程實(shí)踐
3.1 類繼承
[強(qiáng)制] 如果一個類沒有基類,必須繼承自object類。
class SampleClass(object): pass class OuterClass(object): class InnerClass(object): pass class ChildClass(ParentClass): """Explicitly inherits from another class already."""
3.2 字符串格式化與拼接
[強(qiáng)制] 除了a+b這種最簡單的情況外,應(yīng)該使用%或format格式化字符串。
[強(qiáng)制] 不要使用+=拼接字符串列表,應(yīng)該使用join。
x = '%s, %s!' % (imperative, expletive) x = '{}, {}!'.format(imperative, expletive) x = 'name: %s; score: %d' % (name, n) x = 'name: {}; score: {}'.format(name, n)
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)
3.3 文件和socket
[強(qiáng)制] 用完文件或socket后必須顯式關(guān)閉句柄。建議使用with語法簡化開發(fā)
with open("hello.txt") as hello_file: for line in hello_file: print line
3.4 主程序
[強(qiáng)制] 所有module都必須可導(dǎo)入。如需要執(zhí)行主程序,必須檢查__name__ == '__main__'
3.5 單元測試
[建議] 推薦使用PyUnit做單元測試。是否需要做單元測試以及目標(biāo)單測覆蓋率由項目負(fù)責(zé)人自行決定。
[建議] 推薦測試代碼放在單獨(dú)的test目錄中。如果被測試代碼文件名為xxx.py,那么測試代碼文件應(yīng)該被命名為xxx_test.py#
# math_test.py
# !/usr/bin/env python # -*- coding: gb18030 -*- import unittest import math class MathTestCase(unittest.TestCase): def test_sqrt(self): self.assertEqual(math.sqrt(4) * math.sqrt(4), 4) if __name__ == "__main__": unittest.main()
3.4 日志輸出
[建議] 推薦使用python自帶的logging庫打印日志。
[建議] 推薦默認(rèn)日志格式:"%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s", 時間格式:"%Y-%m-%d %H:%M:%S"
[建議] 推薦線上程序使用兩個日志文件:一個專門記錄warning/error/critical日志,另一個記錄所有日志。
# log.py import os import logging import logging.handlers def init_log(log_path, level=logging.INFO, when="D", backup=7, format="%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s", datefmt="%m-%d %H:%M:%S"): """ init_log - initialize log module Args: log_path - Log file path prefix. Log data will go to two files: log_path.log and log_path.log.wf Any non-exist parent directories will be created automatically level - msg above the level will be displayed DEBUG < INFO < WARNING < ERROR < CRITICAL the default value is logging.INFO when - how to split the log file by time interval 'S' : Seconds 'M' : Minutes 'H' : Hours 'D' : Days 'W' : Week day default value: 'D' format - format of the log default format: %(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s INFO: 12-09 18:02:42: log.py:40 * 139814749787872 HELLO WORLD backup - how many backup file to keep default value: 7 Raises: OSError: fail to create log directories IOError: fail to open log file """ formatter = logging.Formatter(format, datefmt) logger = logging.getLogger() logger.setLevel(level) dir = os.path.dirname(log_path) if not os.path.isdir(dir): os.makedirs(dir) handler = logging.handlers.TimedRotatingFileHandler(log_path + ".log", when=when, backupCount=backup) handler.setLevel(level) handler.setFormatter(formatter) logger.addHandler(handler) handler = logging.handlers.TimedRotatingFileHandler(log_path + ".log.wf", when=when, backupCount=backup) handler.setLevel(logging.WARNING) handler.setFormatter(formatter) logger.addHandler(handler)

浙公網(wǎng)安備 33010602011771號