編寫高質量代碼--改善python程序的建議
來自: http://www.cnblogs.com/cotyb/p/5171527.html
原文發表在我的 博客主頁 ,轉載請注明出處!
建議四十一:一般情況下使用ElementTree解析XML
python中解析XML文件最廣為人知的兩個模塊是 xml.dom.minidom 和 xml.sax ,作為主要解析XML方法的兩種實現,DOM需要將整個XML文件加載到內存中并解析為一棵樹,簡單但是內存消耗大;SAX是基于事件驅動的,雖不需要全部裝入XML文件,但是處理過程復雜。一般情況下選擇 ElementTree 便可以, cElementTree 是其Cython實現,速度更快,消耗內存更少,性能上更好。使用 ElementTree 的特性有:
- 使用簡單,它將整個XML文件以樹的形式展示,每一個元素的屬性以字典的形式表示,非常方便處理
- 內存上消耗明顯低于DOM解析,在底層進行了一定的優化,解析工具支持SAX事件驅動
- 支持 XPath 查詢,非常方便獲取任意節點的值
建議四十二:理解模塊pickle優劣
python中有很多支持序列化的模塊,像pickle,json等
序列化,就是把內存中的數據結構在不丟失其身份和類型信息的情況下轉成對象的文本或二進制表示的過程,比如在磁盤上保存當前程序的狀態數據以便重啟的時候能夠重新加載,多用戶或者分布式系統中數據結構的網絡傳輸時,可以將數據序列化后發送給一個可信網絡對端,接收者進行反序列化后便可以重新恢復相同的對象等
pickle是最通用的序列化模塊了,他主要有兩個函數 dump() 和 load() ,分別用來進行對象的序列化和反序列化,函數定義如下:
- pickle.dump(obj, file[, protocol]):序列化數據到一個文件描述符(一個打開的文件、套接字等)。參數obj表示需要序列化的對象,包括布爾、數字、字符串、字節數組、None、列表、元祖、字典和集合等基本數據類型。參數file支持write()方法的文件句柄,可以為真實的文件,也可以是StringIO對象等
- pickle.load(file):表示把文件中的對象恢復為原來的對象
import cPickle as pickle my_data = {"name" : "Python", "type" : "Language", "version" : "2.7.5"}fp = open("picklefile.dat","wb") pickle.dump(my_data, fp) fp.close()
fp = open("picklefile.dat","rb") out = pickle.load(fp) fp.close() print out print type(out)</pre>
pickle擁有良好的特性:
- 接口簡單,容易使用
- pickle的存儲格式具有通用性,能夠被不同平臺的python解析器共享
- 支持的數據類型廣泛
- pickle模塊是可以擴展的
- 能夠自動維護對象間的引用,如果一個對象上存在多個引用,pickle后不會改變對象間的引用,并且能夠自動處理循環和遞歸引用
import cpickle as pickle a = [1, 2] b = a b.append(3) p = pickle.dumps((a, b)) a1, b1 = pickle.loads(p) print a1, b1 a1.append(4) print b1
建議四十三:序列化的另一個不錯的選擇——JSON
JSON(JavaScript Object Notation)是一種輕量級數據交換格式。相對于上文提到的pickle,JSON有如下優勢:
</div>
- 使用簡單,支持多種數據類型,JSON文檔的構成非常簡單,僅存在兩大數據結構
- 名稱/值對的集合
- 值的有序列表
- 存儲格式可讀性更為友好,容易修改
- JSON支持跨平臺跨語言操作,能夠輕易被其他語言解析,而pickle只能在python語言中使用,另外相比于pickle,JSON的存儲格式更為緊湊,所占空間更小
- 具有較強的擴展性,JSON模塊還提供了編碼和解碼類,以便用戶對其默認不支持的序列化類型進行擴展
* 建議四十四:使用traceback獲取棧信息**
首先來看一個簡單的例子:
gList = ['a','b','c','d','e','f','g'] def f(): gList[5] return g() def g(): return h() def h(): del gList[2] return i() def i(): gList.append('i') print gList[7] if __name__ == '__main__': try: f() except IndexError as ex: print "Sorry,Exception occured,you accessed an element out of range" print ex
這個例子比較簡單,開發人員也為自己和用戶打印出了錯誤信息,但是如果要debug,怎么才能快速地知道錯誤發生在哪里呢?traceback模塊可以滿足這個需求,它會輸出完整的棧信息,將上面的代碼修改下:
except IndexError as ex: print "Sorry,Exception occured,you accessed an element out of range" print ex traceback.print_exc()
再次運行,程序會輸出發生異常時候完整的棧信息,包括調用順序、異常發生的語句、錯誤類型等。
traceback.print_exc()方法打印出的信息包括3部分:錯誤類型、錯誤對應的值以及具體的trace信息,包括文件名、具體的行號、函數名以及對應的源代碼。
*
建議四十五:使用logging記錄日志信息**
logging模塊提供了日志功能,將logger的level分為5個級別,如下圖,可以通過Logger.setLevel(lvl)來設置,默認的為WARNING
logging lib包含了以下4個主要對象:
</div>
- logger logger是程序信息輸出的接口,分散在不同的代碼中,使得程序可以在運行的時候記錄相應的信息,根據設置的日志級別或filter來決定哪些信息需要輸出,并將這些信息分發到其關聯的handler。
- Handler 用來處理信息的輸出,可以將信息輸出到控制臺、文件或者網絡。
- Formatter 決定log信息的格式,格式類似于“%(
)s” - Filter 決定哪些信息需要輸出
關于logging的使用:
- 盡量為logging取一個名字而不是采用默認,這樣擋在不同的模塊中使用的時候,其他模塊只需要使用一下代碼就可以方便地使用同一個logger。
import logging logging.basicConfig(level = logging.DEBUG) logger = logging.getLogger(__name__)
- 為了方便地找出問題所在,logging的名字建議以模塊或者class來命名
- logging是線程安全的,不支持多進程寫入同一個文件
* 建議四十六:使用threading模塊編寫多線程程序**
GIL使得python多線程編程暫時無法充分利用多處理器的優勢,對于只含純python的代碼也許并不能提高運行效率,但是在以下情況中,比如等待外部資源返回,為了提高用戶體驗建立反應靈活的用戶界面還是可以使用的。
python提供了thread和threading兩個關于多線程的模塊: - thread模塊提供了多線程底層支持模塊,以低級原始的方式來處理和控制線程,使用復雜
- threading模塊基于thread進行包裝,將線程的操作對象化,在語言層面提供了豐富的特性
- threading模塊對同步原語的支持更為完善和豐富
- threading模塊在主線程和子線程交互上更為友好,看一個例子:
</ul>
import threading, time,sys class test(threading.Thread): def __init__(self,name,delay): threading.Thread.__init__(self) self.name = name self.delay = delay def run(self): print "%s delay for %s" %(self.name,self.delay) time.sleep(self.delay) c = 0 while True: print "This is thread %s on line %s" %(self.name,c) c = c + 1 if c == 3: print "End of thread %s" % self.name break t1 = test('Thread 1', 2) t2 = test('Thread 2', 2) t1.start() print "Wait t1 to end" t1.join() t2.start() print 'End of main'
- thread模塊不支持守護線程,thread模塊中主線程退出的時候,所有的子線程不論是否還在工作,都會被強制結束,并且沒有任何警告,也沒有任何退出前的清理工作,比如:
</ul>
#coding=utf-8 from thread import start_new_thread import time def myfunc(a,delay): print "I will calculate square of %s after delay for %s" %(a,delay) time.sleep(delay) print "calculate begins..." result = a*a print result return result start_new_thread(myfunc,(2,5))# 同時啟動兩個線程 start_new_thread(myfunc,(6,8)) time.sleep(1)
主線程沒有考慮子線程就退出了,可以用threading解決,如下:
import threading import time def myfunc(a,delay): print "I will calculate square of %s after delay for %s" %(a,delay) time.sleep(delay) print "calculate begins..." result = a*a print result return result t1=threading.Thread(target=myfunc,args=(2,5)) t2=threading.Thread(target=myfunc,args=(6,8)) print t1.isDaemon() print t2.isDaemon() t2.setDaemon(True) t1.start() t2.start()
建議四十七:使用Queue使多線程編程更安全
多線程從來就不是一個簡單的問題,但是Queue卻可以保障安全,而且不需要加鎖,以生產者和消費者為例,看代碼:
</div>
#!usr/bin/pythoncoding=utf-8
import Queue import threading import random writelock = threading.Lock() # 創建鎖對象用于控制輸出 class Producer(threading.Thread): def init(self, q,name): super(Producer, self).init() self.q = q self.name = name print "Producer "+self.name+" Started" def run(self): while 1: if self.q.full(): # 隊列滿 print 'Queue is full,producer wait!' else: value = random.randint(0,10) print self.name +" put value: " + str(value)+ "into queue" self.q.put((self.name+":"+str(value))) # 放入隊列中
class Consumer(threading.Thread): # 消費者 def init(self, q,name): super(Consumer, self).init() self.q = q self.name = name print "Consumer "+self.name+" started\n " def run(self): while 1: if self.q.empty(): # 隊列為空 print 'queue is empty,consumer wait!' else: value = self.q.get() # 獲取一個元素 print self.name +"get value"+\ value + " from queue"
if name == "main": q = Queue.Queue(10) p = Producer(q,"P1") p.start() p1 = Producer(q,"P2") p1.start() c1 = Consumer(q,"C1") c1.start() q.join()</pre>
python中的Queue模塊提供了三種隊列:
- Queue.Queue():先進先出
- Queue.LifoQueue():先進后出
- Queue.PriorityQueue():優先級隊列
參考:編寫高質量代碼--改善python程序的91個建議
</div>