改善python的91个建议(二):库
sorted()函数:返回一个新的列表,原列表不变
a = [1, 2, 4, 2, 3] sorted(a) # OUT: [1, 2, 2, 3, 4] a # OUT: [1, 2, 4, 2, 3]
列表的sort方法:原地修改列表,返回None
a = [1, 2, 4, 2, 3] a.sort() a # OUT: [1, 2, 2, 3, 4]
sorted()函数:接受的参数,除了列表,其它的一元数组都可以,返回的都是一个新的列表
b = (1, 2, 4, 2, 3) # 接受对象元组 sorted(b) [1, 2, 2, 3, 4] b = 'asdfasdf' # 接受对象字符串,当然字符串排序一般没什么意思 # OUT: ['a', 'a', 'd', 'd', 'f', 'f', 's', 's']
建议 39: 使用 Counter 进行计数统计
常见的计数统计可以使用dict、defaultdict、set和list,不过 Python 提供了一个更优雅的方式:
>>> from collections import Counter >>> some_data = {'a', '2', 2, 3, 5, 'c', '7', 4, 5, 'd', 'b'} >>> Counter(some_data) Counter({'7',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '2': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
Counter 类属于字典类的子类,是一个容器对象,用来统计散列对象,支持+、-、&、|,其中&和|分别返回两个 Counter 对象各元素的最小值和最大值。
# 初始化 Counter('success') Counter(s=3, c=2, e=1, u=1) Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1}) # 常用方法 list(Counter(some_data).elements()) # 获取 key 值 Counter(some_data).most_common(2) # 前 N 个出现频率最高的元素以及对应的次数 (Counter(some_data))['y'] # 访问不存在的元素返回 0 c = Counter('success') c.update('successfully') # 更新统计值 c.subtract('successfully') # 统计数相减,允许为0或为负
建议 40:深入掌握 ConfigParser
几乎所有的应用程序都会读取配置文件,ini是一种比较常见的文件格式:Python 提供标准库 ConfigParser 来支持它:
import ConfigParser conf = ConfigParser.ConfigParser() conf.read('example.conf') print(conf.get('section1', 'in_default'))
再来看个SQLAlchemy配置文件的例子format.ini:
[DEFAULT] conn_str = %(dbn)s://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s dbn = mysql user = root host = localhost port = 3306 [db1] user = aaa pw = ppp db = example [db2] host = 192.168.0.110 pw = www db = example
import ConfigParser conf = ConfigParser.ConfigParser() conf.read('format.conf') print(conf.get('db1', 'conn_str')) print(conf.get('db2', 'conn_str'))
建议 41:使用 argparse 处理命令行参数
Python 标准库中有几种关于处理命令行的方案:getopt、optparse、argparse。
现阶段最好用的参数处理是argparse:
import argparse parse = argparse.ArgumentParser() parse.add_argument('-o', '--output') parse.add_argument('-v', dest='verbose', action='store_true') args = parse.parse_args()
建议 42:使用 pandas 处理大型 CSV 文件
CSV文件: 作为一种逗号分隔型值的纯文本格式文件,常用于数据库数据的导入导出,数据分析中记录的存储。
Python 中的 csv 模块提供了对 CSV 的支持。
# 用法: csv.reader(csvfile[, dialect='excel'][, fmtparam]) # 读取一个 csv 文件,返回一个 reader 对象 csv.writer(csvfile, dialect='excel', **fmtparams) # 写入 csv 文件 csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel') # 示例: import csv reader = csv.reader('example.csv', dialect='excel') # 读取一个 csv 文件,返回一个 reader 对象 reader.next() for row in reader: pass writer=csv.writer(open('example.csv', 'a'), dialect='excel') # 写入 csv 文件 writer.writerow('asdfsf')
处理 CSV 还有更好的选择,那就是大名鼎鼎的 Pandas,它提供两种基本的数据结构:Series 和 DataFrame。这里有个 Pandas 的教程,值得一看。
建议 43:一般情况下使用 ElementTree 解析 XML
给一个较好的学习教程,下面直接看例子吧:
count = 0 for event, elem in ET.iterparse('test.xml'): if event == 'end': if elem.tag == 'userid': count += 1 elem.clear() print(count)
建议 45:序列化的另一个不错的选择 JSON
建议 46:使用 traceback 获取栈信息
当发生异常,开发人员往往需要看到现场信息,trackback 模块可以满足这个需求,先列几个常用的:
traceback.print_exc() # 打印错误类型、值和具体的trace信息 traceback.print_exception(type, value, traceback[, limit[, file]]) # 前三个参数的值可以从sys.exc_info() raceback.print_exc([limit[, file]]) # 同上,不需要传入那么多参数 traceback.format_exc([limit]) # 同 print_exc(),返回的是字符串 traceback.extract_stack([file, [, limit]]) # 从当前栈中提取 trace 信息
traceback 模块获取异常相关的数据是通过sys.exc_info()得到的,该函数返回异常类型type、异常value、调用和堆栈信息traceback组成的元组。
同时 inspect 模块也提供了获取 traceback 对象的接口。
学习笔记三:改善Python程序的91个建议
许久没更新,四月份学校又加了几门课程,我自己又去报名了驾校,时间没有安排过来。不知不觉已经五月了,希望一切能够回归正轨。同时又给自己加了两项任务:夜跑和写日记,望能够坚持下去。有始有终,今天更新完这个学习笔记系列,主要的原因是知乎不支持 markdown,再次吐槽!
第 4 章 库
建议 41:使用 argparse 处理命令行参数
Python 标准库中有几种关于处理命令行的方案:getopt、optparse、argparse。
现阶段最好用的参数处理是argparse:
import argparse
parse = argparse.ArgumentParser()
parse.add_argument('-o', '--output')
parse.add_argument('-v', dest='verbose', action='store_true')
args = parser.parse_args()
关于命令行参数,我记得有个第三方库超好用,好久贴个教程出来。
建议 42:使用 pandas 处理大型 CSV 文件
CSV 作为一种逗号分隔型值的纯文本格式文件,常用于数据库数据的导入导出,数据分析中记录的存储。Python 中的 csv 模块提供了对 CSV 的支持。
列出一些常用的 API:
reader(csvfile[, dialect='excel'][, fmtparam]) # 读取一个 csv 文件,返回一个 reader 对象
csv.writer(csvfile, dialect='excel', **fmtparams) # 写入 csv 文件
csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel')
当然,处理 CSV 还有更好的选择,那就是大名鼎鼎的 Pandas,它提供两种基本的数据结构:Series 和 DataFrame。这里有个 Pandas 的教程,值得一看。
建议 43:一般情况下使用 ElementTree 解析 XML
给一个较好的学习教程,下面直接看例子吧:
count = 0
for event, elem in ET.iterparse('test.xml'):
if event == 'end':
if elem.tag == 'userid':
count += 1
elem.clear()
print(count)
建议 44:理解模块 pickle 优劣
pickle 是较为通用的序列化模块,其中两个主要的函数dump()和load()分别用来进行对象的序列化和反序列化:
-
pickle.dump(obj, file[, protocol])
-
load(file)
In [1]: import pickle
In [2]: data = {'name': 'Python', 'type': 'Language', 'version': '3.5.2'}
In [3]: with open('pickle.dat', 'wb') as fp:
...: pickle.dump(data, fp)
...:
In [4]: with open('pickle.dat', 'rb') as fp:
...: out = pickle.load(fp)
...: print(out)
...:
{'version': '3.5.2', 'name': 'Python', 'type': 'Language'}
它还有个C语言的实现 cPickle,性能较好。但 pickle 限制较多:比如不能保证原子性操作,存在安全问题,跨语言兼容性不好等。
建议 45:序列化的另一个不错的选择 JSON
这个应该不用多做介绍了吧,书中讲得比较浅,又来放链接(逃...
建议 46:使用 traceback 获取栈信息
当发生异常,开发人员往往需要看到现场信息,trackback 模块可以满足这个需求,先列几个常用的:
traceback.print_exc() # 打印错误类型、值和具体的trace信息
traceback.print_exception(type, value, traceback[, limit[, file]]) # 前三个参数的值可以从sys.exc_info()
raceback.print_exc([limit[, file]]) # 同上,不需要传入那么多参数
traceback.format_exc([limit]) # 同 print_exc(),返回的是字符串
traceback.extract_stack([file, [, limit]]) # 从当前栈中提取 trace 信息
traceback 模块获取异常相关的数据是通过sys.exc_info()得到的,该函数返回异常类型type、异常value、调用和堆栈信息traceback组成的元组。
同时 inspect 模块也提供了获取 traceback 对象的接口。
建议 47:使用 logging 记录日志信息
仅仅将信息输出到控制台是远远不够的,更为常见的是使用日志保存程序运行过程中的相关信息,如运行时间、描述信息以及错误或者异常发生时候的特定上下文信息。Python 提供 logging 模块提供了日志功能,将日志分为 5 个级别:
Level使用情形DEBUG详细的信息,在追踪问题的时候使用INFO正常的信息WARNING一些不可预见的问题发生,或者将要发生,如磁盘空间低等,但不影响程序的运行ERROR由于某些严重的问题,程序中的一些功能受到影响CRITICAL严重的错误,或者程序本身不能够继续运行之前完成过一个个人博客,总算对日志消息有了一定的了解。总的来说,日志消息是给程序员看的,在开发中,我们需要看到程序运行时的方方面面的情况,这时候给日志分级就派上用场,其实日志消息是由我们来决定它属于哪一种类型。
logging.basicConfig([**kwargs]) 提供对日志系统的基本配置:
格式描述filename指定 FileHandler 的文件名,而不是默认的 StreamHandlerfilemode打开文件的模式,同 open 函数中的同名参数,默认为 'a'format输出格式字符串datefmt日期格式level设置根 logger 的日志级别stream指定 StreamHandler。这个参数若与 filename 冲突,忽略 stream下面结合 traceback 和 logging 来记录程序运行过程中的异常:
import traceback import sys import logging gList = ["a", "b", "c", "d", "e", "f", "g"] logging.basicConfig( # 配置日志的输出方式及格式 level = logging.DEBUG, filename = "log.txt", filemode = "w", format = "%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s % (message)s", ) def f(): gList[5] logging.info("[INFO]:calling method g() in f()") # 记录正常的信息 return g() def g(): logging.info("[INFO]:calling method h() in g()") return h() def h(): logging.info("[INFO]:Delete element in gList in h()") del gList[2] logging.info("[INFO]:calling method i() in h()") return i() def i(): logging.info("[INFO]:Append element i to gList in i()") gList.append("i") print(gList[7]) if __name__ == "__main__": logging.debug("Information during calling f():") try: f() except IndexError as ex: print("Sorry, Exception occured, you accessed an element out of range") # traceback.print_exc() ty, tv, tb = sys.exc_info() logging.error("[ERROR]: Sorry, Exception occured, you accessed an element out of range") # 记录异常错误消息 logging.critical("object info:%s" % ex) logging.critical("Error Type:{0}, Error Information:{1}".format(ty, tv)) # 记录异常的类型和对应的值 logging.critical("".join(traceback.format_tb(tb))) # 记录具体的 trace 信息 sys.exit(1)
logging 模块让我们可以很方便地控制日志信息,如loggging.disable()传入一个日志级别会禁用该级别或比级别更低的日志消息,默认是全部禁用。大致我们常用的日志记录就这些了。
建议 48:使用 threading 模块编写多线程程序
之前学习廖老师的 Python3 教程的时候,关于线程有句话记得特别清楚:
多线程的并发在Python中就是一个美丽的梦。
由于 GIL 的存在,让 Python 多线程编程在多核处理器中无法发挥优势,但在一些使用场景下使用多线程仍然比较好,如等待外部资源返回,或建立反应灵活的用户界面,或多用户程序等。
Python3 提供了两个模块:_thread和threading。_thread提供了底层的多线程支持,使用比较复杂,下面我们重点说说threading。
Python 多线程支持用两种方式来创建线程:一种通过继承 Thread 类,重写它的run()方法;另一种是创建一个 threading.Thread 对象,在它的初始化函数__init__()中将可调用对象作为参数传入。
threading模块中不仅有 Lock 指令锁,RLock 可重入指令锁,还支持条件变量 Condition、信号量 Semaphore、BoundedSemaphore 以及 Event 事件等。
下面有一个比较经典的例子来理解多线程:
import threading
from time import ctime,sleep
def music(func):
for i in range(2):
print("I was listening to %s. %s" % (func,ctime()))
sleep(1) # 程序休眠 1 秒
def move(func):
for i in range(2):
print("I was at the %s! %s" % (func,ctime()))
sleep(5)
threads = []
t1 = threading.Thread(target=music,args=('爱情买卖',))
threads.append(t1)
t2 = threading.Thread(target=move,args=('阿凡达',))
threads.append(t2)
if __name__ == '__main__':
for t in threads:
t.setDaemon(True) # 声明线程为守护线程
t.start()
#3
print("all over %s" % ctime())
以下是运行结果:
I was listening to 爱情买卖. Tue Apr 4 17:57:02 2017
I was at the 阿凡达! Tue Apr 4 17:57:02 2017
all over Tue Apr 4 17:57:02 2017
分析:threading 模块支持线程守护,我们可以通过setDaemon()来设置线程的daemon属性,当其属性为True时,表明主线程的退出可以不用等待子线程完成,反之,daemon属性为False时所有的非守护线程结束后主线程才会结束,那运行结果为:
I was listening to 爱情买卖. Tue Apr 4 18:05:26 2017
I was at the 阿凡达! Tue Apr 4 18:05:26 2017
all over Tue Apr 4 18:05:26 2017
I was listening to 爱情买卖. Tue Apr 4 18:05:27 2017
I was at the 阿凡达! Tue Apr 4 18:05:31 2017
继续修改代码,当我们在#3处加入t.join(),此方法能够阻塞当前上下文环境,直到调用该方法的线程终止或到达指定的 timeout,此时在运行程序:
I was listening to 爱情买卖. Tue Apr 4 18:08:15 2017
I was at the 阿凡达! Tue Apr 4 18:08:15 2017
I was listening to 爱情买卖. Tue Apr 4 18:08:16 2017
I was at the 阿凡达! Tue Apr 4 18:08:20 2017
all over Tue Apr 4 18:08:25 2017
当我们把music函数的休眠时间改为 4 秒,再次运行程序:
I was listening to 爱情买卖. Tue Apr 4 18:11:16 2017
I was at the 阿凡达! Tue Apr 4 18:11:16 2017
I was listening to 爱情买卖. Tue Apr 4 18:11:20 2017
I was at the 阿凡达! Tue Apr 4 18:11:21 2017
all over Tue Apr 4 18:11:26 2017
此时我们就可以发现多线程的威力了,music虽然增加了 3 秒,然而总的运行时间仍然为 10 秒。
建议 49:使用 Queue 使多线程编程更加安全
线程间的同步和互斥,线程间数据的共享等这些都是涉及线程安全要考虑的问题。纵然 Python 中提供了众多的同步和互斥机制,如 mutex、condition、event 等,但同步和互斥本身就不是一个容易的话题,稍有不慎就会陷入死锁状态或者威胁线程安全。
如何保证线程安全呢?我们先来看看 Python 中的 Queue 模块:
-
Queue.Queue(maxsize):先进先出,maxsize 为队列大小,其值为非正数的时候为无限循环队列
-
Queue.LifoQueue(maxsize):后进先出,相当于栈
-
Queue.PriorityQueue(maxsize):优先级队列
以上队列所支持的方法:
-
Queue.qsize():返回近似的队列大小。当该值 > 0 的时候并不保证并发执行的时候 get() 方法不被阻塞,同样,对于 put() 方法有效。
-
Queue.empty():队列为空的时候返回 True,否则返回 False
-
Queue.full():当设定了队列大小的情况下,如果队列满则返回 True,否则返回 False
-
Queue.put(item[, block[, timeout]]):往队列中添加元素 item,block 设置为 False 的时候,如果队列满则抛出 Full 异常。如果 block 设置为 True,timeout 为 None 的时候则会一直等待直到有空位置,否则会根据 timeout 的设定超时后抛出 Full 异常
-
Queue.put_nowait(item):等于 put(item, False).block 设置为 False 的时候,如果队列空则抛出 Empty 异常。如果 block 设置为 True、timeout 为 None 的时候则会一直等到有元素可用,否则会根据 timeout 的设定超时后抛出 Empty 异常
-
Queue.get([block[, timeout]]):从队列中删除元素并返回该元素的值
-
Queue.get_nowait():等价于 get(False)
-
Queue.task_done():发送信号表明入列任务已经完成,经常在消费者线程中用到
-
Queue.join():阻塞直至队列中所有的元素处理完毕
首先 Queue 中的队列和 collections.deque 所表示的队列并不一样,前者用于不同线程之间的通信,内部实现了线程的锁机制,后者是数据结构上的概念,支持 in 方法。
Queue 模块实现了多个生产者多个消费者的队列,当多线程之间需要信息安全的交换的时候特别有用,因此这个模块实现了所需要的锁原语,为 Python 多线程编程提供了有力的支持,它是线程安全的。
先来看一个简单的例子:
import os import Queue import threading import urllib2 class DownloadThread(threading.Thead): def __init__(self, queue): threading.Thread.__init__(self) self.queue = queue def run(self): while True: url = self.queue.get() print('{0} begin download {1}...'.format(self.name, url)) self.download_file(url) self.queque.task_done() print('{0} download completed!!!'.format(self.name)) def download_file(self, url): urlhandler = urllib2.urlopen(url) fname = os.path.basename(url) + '.html' with open(fname, 'wb') as f: while True: chunk = urlhandler.read(1024) if not chunk: break f.write(chunk) if __name__ == '__main__': urls = ['http://wiki.python.org/moin/WebProgramming', 'https://www.createspace.com/3611970', 'http://wiki.python.org/moin/Documentation' ] queue = Queue.Queue() for i range(5): t = DownloadThread(queue) t.setDaemon(True) t.start() for url in urls: queue.put(url) queue.join()
posted on 2014-03-28 12:16 myworldworld 阅读(243) 评论(0) 收藏 举报