python編碼規范
1. 前言
1.1. 一般信息[重要必讀]
此編碼風格指南主要基於 Google Python Style Guide [中譯版],結合百度python使用習慣和實際開發情況制定。
這份文檔存在的意義是讓大家寫出統一風格的代碼,讓百度的模塊可維護性和可讀性更好;
文檔內容可能會與您的喜好沖突, 請盡量用包容的心態來接受; 不合理之處, 請反饋給py-styleguide@baidu.com
1.2. 如何使用本編程規范
1.2.1. 本規范的層次結構
本規范可分為三大部分,分別對Python語法、風格、編程實踐作出規定與建議。
每一部分有若干專題,每一專題下有若干條目。
條目是規范的基本組成部分,每一條目由規定、定義、解釋、示例、參考等項組成。
1.2.2. 條目的級別和編號
- 本規范的條目分兩個級別
-
·[強制]:要求所有程序必須遵守,不得違反·[建議]:建議遵守,除非確有特殊情況
1.3. The Zen of Python
建議每位python開發人員細讀”python之禪”,理解規范背后的思想
"""
The Zen of Python Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than |right| now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those! -- by Tim Peters """
2. 語言規范
2.1. import
·[強制] 禁止使用from xxx import yyy語法直接導入類或函數(即yyy只能是module或package,不能是類或函數)。
·[強制] [PY002] 禁止使用from xxx import *
·[強制] [PY003] import時必須使用package全路徑名(相對PYTHONPATH),禁止使用相對路徑(相對當前路徑)。
2.2. 異常
·[建議] 可以使用異常。但使用前請務必詳細了解異常的行為,謹慎使用
·[強制] [PY004] 禁止使用雙參數形式(raise MyException, 'Error Message')或字符串形式(raise 'Error Message')語法拋異常。
·[強制] 如果需要自定義異常,應該在模塊內定義名為Error的異常基類。該基類必須繼承自Exception。其它異常類都從該Error類派生而來。
·[強制] 除非重新拋出異常,禁止使用except:捕獲所有異常。
·[建議] 除非重新拋出異常,否則不建議捕獲Exception或StandardError。如果捕獲,必須在日志中記錄所捕獲異常信息。
·[強制] [PY007] 捕捉異常時,應當使用as語法,禁止使用逗號語法。
·[建議] 建議try中的代碼盡可能少。避免catch住未預期的異常,掩藏掉真正的錯誤。
·[建議] 建議使用finally子句來執行那些無論try塊中有沒有異常都應該被執行的代碼,這對於清理資源常常很有用。例如:文件關閉。
解釋
- raise MyException,'Error Message'或raise 'Error Message'語法將廢棄
- 用戶經常需要以統一的方式處理某模塊拋出的所有異常,如果模塊開發者自定義的異常沒有統一基類,用戶處理起來就很麻煩
- except:或except Exception:或except StandardError:會捕獲類似語法錯誤、Ctrl+C中斷等底層異常。而一般情況下這不是用戶代碼要處理的
- 逗號式異常捕獲語法將廢棄
示例
YES:
raise MyException
raise MyException('Error Message')
class Error(Exception):
pass
try:
raise Error
except Error as error:
pass
NO:
raise 'Error Message'
raise MyException, 'Error Message'
try:
raise Error
except Error, error:
pass
2.3. 全局變量
- ·[強制] 禁止使用全局變量。除了以下例外:
-
- 腳本默認參數
- 模塊級常量
·[強制] 如果定義全局變量,必須寫在文件頭部。
解釋
- 全局變量破壞程序封裝性,使代碼維護困難
2.4. 構造函數
·[建議] 類構造函數應該盡量簡單,不能包含可能失敗或過於復雜的操作
解釋
- 復雜的、可能出異常的構造函數通常語義不明確,與調用者的期望不符。且難以維護,容易出錯。一般建議把復雜部分拆分到獨立的函數中。
- 什么情況算”過於”復雜,由代碼owner和reviewer根據經驗自行判定
2.5. 函數返回值
·[強制] [PY004] 函數返回值必須小於等於3個。3個以上時必須通過class/namedtuple/dict等具名形式進行包裝
解釋
- 雖然python支持以tuple的形式返回多個值,但返回值過多時需要用戶記住返回值順序,使接口難以使用,容易用錯
示例
YES:
def get_numbers():
return 1, 2, 3
a, b, c = get_numbers()
class Person(object):
def __init__(self, name, gender, age, weight):
self.name = name
self.gender = gender
self.age = age
self.weight = weight
or
import collections
Person = collections.namedtuple('Person', 'name gender age weight')
def get_person_info():
return Person('jjp', 'MALE', 30, 130)
person = get_person_info()
NO:
def get_numbers():
return 1, 2, 3, 4
a, b, c, d = get_numbers()
def get_person_info():
return 'jjp', 'MALE', 30, 130
name, gender, age, weight = get_person_info()
2.6. 嵌套/局部/內部類或函數
·[建議] 不推薦使用嵌套/局部/內部類或函數
解釋
- 這些機制增加了代碼理解難度,考慮到公司的平均python水平,不推薦使用
2.7. 列表推導
·[強制] [PY011] 可以使用列表推導。mapping、loop、filter部分單獨成行,且最多只能寫一行。禁止多層loop或filter。
解釋
- 復雜的列表推導難以理解,建議轉換成對應的for循環
示例
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:
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)
2.8. 默認迭代器和操作符
·[強制] 對容器或文件的只讀遍歷,應該使用內置的迭代方法,不要使用返回list的方式遍歷。
·[強制] [PY013] 對容器類型,使用in或not in判斷元素是否存在。而不是has_key。
解釋
-
- 可讀性和性能更好。例如:
-
- file.readlines()會一次性讀入文件中所有行,存儲到一個list中返回。當文件很大時,性能開銷較大
-
當文件或容器較小時,調用返回list的函數也可以接受。請reviewer視具體情況決定是否通過。
-
如果遍歷過程中需要對容器進行增刪,請使用返回list的方式遍歷
示例
Yes:
for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in dict.iteritems(): ...
# 刪除畢業學生
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(): ...
#刪除畢業學生
for id in students:
if students[id].graduated:
del students[id] # 拋出RuntimeError異常
2.9. 生成器
·[建議] 當返回較長列表數據時建議使用yield和generator函數。
解釋
- 生成器函數可以避免返回大列表,占用過多內存、影響性能。同時還可以保持代碼的優雅易讀。
示例
YES:
#返回n以內的奇數
def odds(n):
for i in xrange(1, n + 1):
if i % 2 == 1:
yield i
for i in odds(1000):
print i
NO:
#返回n以內的奇數
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
2.10. lambda函數 
·[強制] [PY014] 可以使用lambda函數,但僅限一行之內。
解釋
- lambda函數可以簡化開發。但復雜的lambda函數可讀性很差,應該避免
2.11. 條件表達式
·[強制] 條件表達式僅用於一行之內,禁止嵌套使用
解釋
- 語法略小眾,其它語言背景開發者(如C++)看起來比較困惑。寫復雜了難以閱讀維護
示例
YES:
# 單行簡單條件表達式
x = true_value if cond else false_value
# 改寫成if形式
if cond:
x = true_value
else
x = false_value
# 復雜if表達式
if t < 60:
unit = "seconds"
elif t < 3600:
unit = "minutes"
else
unit = "hours"
NO:
#嵌套條件表達式
unit = "seconds" if t < 60 else "minutes" if t < 3600 else "hours"
2.12. 默認參數
·[強制] [PY016] 僅可使用以下基本類型字面常量或常量作為默認參數:整數、bool、浮點、字符串、None
解釋
- 以可修改的對象(如list、dict、object等)作為默認參數,可能會被不小心改掉,導致默認值發生變化,產生難以追查的錯誤
- 默認值在module加載時求值,而不是每次調用時求值。復雜的默認值可能和預期不符合(例如下邊例子中的time.time()和FLAGS.my_things )
示例
YES:
def foo(a, b=None):
if b is None:
b = []
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...
...
2.13. 屬性(properties)
·[強制] 可以使用property。但禁止在派生類里改寫property實現。
解釋
- 由於property是在基類中定義的,默認綁定到基類的實現函數。若允許在派生類中改寫property實現,則需要在基類中通過間接方式調用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.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)
area = property(__get_area, __set_area,
doc="""Gets or sets the area of the square.""")
@property
def perimeter(self):
return self.side * 4
NO:
class MySquare(Square):
def __get_area(self):
return math.pi * self.side ** 2 # overwrite doesn't work
def __set_area(self, area):
self.side = math.sqrt(area / math.pi) # overwrite doesn't work
2.14. True/False求值
·[建議] 建議顯式轉換到bool類型,慎用到bool類型的隱式轉換。如使用隱式轉換,你需要確保充分了解其語義
·[強制] [PY018] 禁止使用==或!=判斷表達式是否為None,應該用is或is not None
·[強制] 當明確expr為bool類型時,禁止使用==或!=與True/False比較。應該替換為expr或not expr
·[強制] 判斷某個整數表達式expr是否為零時,禁止使用not expr,應該使用expr == 0
解釋
-
python中None、空字符串、0、空tuple、list、dict都會被隱式轉換為False,這可能和用戶預期的行為不一致。
-
為便於不太熟悉python語言的其它語言開發者理解python代碼,建議”顯式”表明到bool類型的轉換語義
-
運算符==或!=的結果取決於__eq__函數,可能出現obj is not None,但obj==None的情況
-
- if expr != False 當expr為None時,會通過檢測。一般這不是用戶期望的行為
-
-
- from PEP:
-
- Yes: if greeting:
- No: if greeting == True:
- Worse: if greeting is True:
-
-
當判斷expr是否為0時,若expr為None,not expr也會返回True。一般這不是用戶期望的行為
示例
YES:
if users is None or len(users) == 0:
print 'no users'
if foo == 0:
self.handle_zero()
if i % 10 == 0:
self.handle_multiple_of_ten()
Cautious:
if not users:
print "no users"
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()
2.15. 函數和方法裝飾器
- ·[建議] 建議僅在以下幾種情況下使用函數方法裝飾器。其它情況下如有明顯好處,且不影響代碼可維護性,可以謹慎使用
-
- @property、@classmethod、@staticmethod
- 自動化測試
- 第三方庫要求使用的裝飾器
解釋
- decorator太靈活,可以任意改變函數參數和返回值,容易產生非預期行為。濫用容易使代碼可維護性性變差
- decorator在import時執行,decorator代碼執行中的錯誤很難處理和恢復
3. 風格規范
3.1. 分號
·[強制] [PY021] 禁止以分號結束語句
·[強制] [PY022] 一行只能寫一條語句,沒有例外情況
3.2. 行列長度
·[強制] [PY023] 每行不得超過100個字符
·[強制] [PY024] 函數長度不得超過100行
解釋
- 現在寬屏比較流行,所以從傳統的80個字符限制擴展到100個字符
- 函數太長一般說明函數定義不明確,程序結構划分不合理,不利於維護
示例
- 字符串太長時可以分成兩個字符串,python會自動join相臨的字符串
x = ('This will build a very long long ' 'long long long long long long string')
3.3. 括號
·[建議] 除非用於明確算術表達式優先級、tuple或者隱式行連接,否則盡量避免冗余的括號。
解釋
- python傾向於直觀、簡潔的代碼
示例
YES:
if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
return foo
for (x, y) in dict.iteritems(): ...
NO:
if (x):
bar()
if not(x):
bar()
return (foo)
3.4. 縮進
·[強制] 使用4個空格縮進,禁止使用tab縮進。
·[強制] 把單行內容拆成多行寫時,要么與首行保持對齊;要么首行留空,從第二行起統一縮進4個空格;為與后面的代碼區分,可以使用8空格縮進。
解釋
-
不同編輯器對TAB的設定可能不同,使用TAB容易造成在一些編輯器下代碼混亂,所以建議一率轉換成空格。
-
- 在vim下,建議打開如下設置:
-
- :set tabstop=4 設定tab寬度為4個字符
- :set shiftwidth=4 設定自動縮進為4個字符
- :set expandtab 用space自動替代tab
示例
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:
# 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,
...
}
3.5. 空行
·[強制] [PY027] 文件級定義(類或全局函數)之間隔兩個空行,類方法之間隔一個空行
3.6. 空格
·[強制] [PY028] 圓括號、方括號、花括號內側都不加空格
Yes: spam(ham[1], {eggs: 2}, [])
No: spam( ham[ 1 ], { eggs: 2 }, [ ] )
·[強制] [PY029] 參數列表, 索引或切片的左括號前不應加空格
Yes: spam(1)
Yes: dict['key'] = list[index]
No: spam (1)
No: dict ['key'] = list [index]
·[強制] [PY030] 逗號、分號、冒號前不加空格,后邊加一個空格
Yes:
if x == 4:
print x, y
x, y = y, x
No:
if x == 4 :
print x , y
x , y = y , x
·[強制] [PY031] 所有二元運算符前后各加一個空格
Yes: x == 1
No: x<1
·[強制] [PY032] 關鍵字參數或參數默認值里的等號前后不加空格
Yes: def complex(real, imag=0.0): return magic(r=real, i=imag)
No: def complex(real, imag = 0.0): return magic(r = real, i = imag)
3.7. 注釋
·[強制] [PY033] 使用docstring描述module、function、class和method接口。docstring必須用三個雙引號括起來。
·[強制] 對外接口部分必須用docstring描述,內部接口視情況自行決定是否寫docstring。
·[強制][PY034] 接口的docstring描述至少包括功能簡介、參數、返回值。如果可能拋出異常,必須注明。
·[強制] 每個文件都必須有文件聲明,文件聲明必須包括以下信息:版權聲明,功能和用途簡介,修改人及聯系方式。
·[建議] TODO注釋格式必須為:
# TODO: 干什么事情$負責人(郵箱前綴)$最終期限(YYYY-MM-DD)$
定義
- 模塊、類、函數的第一個邏輯行的字符串稱為文檔字符串(docstring)。
解釋
- docstring可以通過help查看,可以通過pydoc自動提取文檔。編寫docstring是個非常好的習慣。
示例
- module注釋(提供的vimrc會自動生成此模板)
################################################################################
# # Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved # ################################################################################ """ This module provide configure file management service in i18n environment. Authors: jiangjinpeng(jiangjinpeng@baidu.com) Date: 2014/04/05 17:23:06 """
- function注釋
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注釋
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."""
- TODO注釋
# TODO: Improve performance using concurrent operation. $jiangjinpeng$2014-04-05$
3.8. import格式
·[強制] [PY037] 每行只能導入一個庫
- ·[強制] 必須按如下順序排列 import,每部分之間留一個空行
-
- 標准庫
- 第三方庫
- 應用程序自有庫
YES:
import os
import sys
from third.party import lib
from third.party import foobar as fb
import my.own.module
NO:
import os, sys
from third.party import lib, foobar
3.9. 命名規則
·[強制] [PY039] 類(包括異常)名使用首字母大寫駝峰式命名
·[強制] 常量使用全大寫字母,單詞間用下划線分隔
·[強制] 其它情況(目錄/文件/package/module/function/method/variable/parameter)一律使用全小寫字母,單詞間用下划線分隔
·[強制] protected成員使用單下划線前綴,private成員使用雙下划線前綴
·[強制] [PY043] 禁止使用雙下划線開頭,雙下划線結尾的名字(類似__init__)
解釋
-
- protected/ private名字解釋
-
- 單/雙下划線開頭的函數或類不會被from module import *導入
- 雙下划線開頭的類成員函數/變量會被python內部改寫,加上類名前綴。以避免與派生類同名成員沖突
- 單/雙下划線都不能真正意義上阻止用戶訪問,只是module開發者與使用者之間的”約定”
-
雙下划線開頭、結尾的名字對python解釋器有特殊意義,可能與內部關鍵字沖突
示例
ClassName, ExceptionName GLOBAL_CONSTANT_NAME, CLASS_CONSTANT_NAME, module_name, package_name, method_name, function_name, global_var_name, instance_var_name, function_parameter_name, local_var_name _InternalClassName, _INTERNAL_CONSTANT_NAME, _internal_function_name, _protected_member_name, __private_member_name
4. 編程實踐
4.1. python解釋器
·[建議] 模塊的主程序必須以#!/usr/bin/env python開頭。如果明確只支持某個python版本,請帶上python版本號
·[建議] 模塊可以自帶某個特定版本的python環境一起發布。需要在程序的啟動腳本中指定具體使用的python解釋器程序
·[建議] 推薦使用2.7版本(含)以上的python解釋器
解釋
- python的安裝位置在不同服務器上可能有差異,所以建議用env python的方式啟動,使用當前環境下的默認python
- 百度不同服務器上安裝的python版本可能有差異,如果對python程序的可移植性要求很高,可以自帶python環境發布
- Python官網的說明:Python 2.x is legacy, Python 3.x is the present and future of the language。但python2和3的差異很大,還有很多程序只支持2.x。而2.7是2.x系列的最后一個版本。因此,我們推薦如果使用2.x系列,盡量選擇2.7。
4.2. 文件編碼
·[強制] 如果文件包含非ASCII字符,必須在文件前兩行標明字符編碼。
·[強制] 只能使用UTF-8或GB18030編碼。推薦使用UTF-8編碼,如果項目確有需要,可以使用GB18030
解釋
- python默認使用ASCII編碼解析代碼文件。若代碼中含有非ASCII字符,無論在字符串中還是注釋中,都會導致python異常。必須在文件頭標明,告知python解釋器正確的字符編碼
- UTF8編碼是自同步編碼,進行字符串處理更方便、通用。但百度內部各種代碼和數據都以GB18030編碼為主,因此也允許使用GB18030編碼。但需要注意GB18030字符串在子串查找時可能匹配到非字符邊界。進行此種操作時建議轉換到unicode或utf-8處理。
# -*- coding: utf-8 -*-
4.3. 類繼承
·[強制] [PY046] 如果一個類沒有基類,必須繼承自object類。
解釋
- 若不繼承自object,property將不能正常工作,且與python3不兼容。
示例
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
4.4. 字符串格式化與拼接
·[強制] 除了a+b這種最簡單的情況外,應該使用%或format格式化字符串。
解釋
- 復雜格式化使用%或format更直觀
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: 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。
解釋
- python中字符串是不可修改對象。每次+=會創建一個新的字符串,性能較差。
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>'
4.5. 文件和socket
·[強制] 用完文件或socket后必須顯式關閉句柄。建議使用with語法簡化開發
解釋
- 雖然文件或socket對象析構時會自動關閉文件,但python什么時候清理對象是不確定的。依賴自動回收可能導致文件句柄耗盡
示例
with open("hello.txt") as hello_file: for line in hello_file: print line
4.6. 主程序
·[強制] 所有module都必須可導入。如需要執行主程序,必須檢查__name__ == '__main__'
解釋
- 若模塊不可導入會導致pydoc或單測框架失敗
示例
def main(): ... if __name__ == '__main__': main()
4.7. 單元測試
·[建議] 推薦使用PyUnit做單元測試。是否需要做單元測試以及目標單測覆蓋率由項目負責人自行決定。
·[建議] 推薦測試代碼放在單獨的test目錄中。如果被測試代碼文件名為xxx.py,那么測試代碼文件應該被命名為xxx_test.py
示例
- 簡單的sqrt測試: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()
參考
4.8. 日志輸出
·[建議] 推薦使用python自帶的logging庫打印日志。
·[建議] 推薦默認日志格式:"%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s", 時間格式:"%Y-%m-%d %H:%M:%S"
·[建議] 推薦線上程序使用兩個日志文件:一個專門記錄warning/error/critical日志,另一個記錄所有日志。
解釋
- 日志格式盡量與百度傳統的ullog/comlog保持一致。方便統一日志處理
- 獨立的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)
- 你可以把上面的代碼拷貝到自己的項目中。在程序初始化時,調用init_log即可使日志打印符合規范
import log def main(): log.init_log("./log/my_program") # 日志保存到./log/my_program.log和./log/my_program.log.wf,按天切割,保留7天 logging.info("Hello World!!!")
參考

