改善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个建议

驭风者驭风者
8 个月前
许久没更新,四月份学校又加了几门课程,我自己又去报名了驾校,时间没有安排过来。不知不觉已经五月了,希望一切能够回归正轨。同时又给自己加了两项任务:夜跑和写日记,望能够坚持下去。有始有终,今天更新完这个学习笔记系列,主要的原因是知乎不支持 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)    收藏  举报

导航