原文发表在我的博客主页,转载请注明出处!
建议四十一:一般情况下使用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)
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有如下优势:
- 使用简单,支持多种数据类型,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个主要对象:
- 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模块在主线程和子线程交互上更为友好,看一个例子:
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模块中主线程退出的时候,所有的子线程不论是否还在工作,都会被强制结束,并且没有任何警告,也没有任何退出前的清理工作,比如:
#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却可以保障安全,而且不需要加锁,以生产者和消费者为例,看代码:
#!usr/bin/python
#coding=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
"
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()
python中的Queue模块提供了三种队列:
- Queue.Queue():先进先出
- Queue.LifoQueue():先进后出
- Queue.PriorityQueue():优先级队列
参考:编写高质量代码--改善python程序的91个建议