今天我们接着上次的话题,继续来探讨一下python在日常编程中有哪些小技巧,骚操作(比如 import this)可以简化我们的代码,提高代码的可读性。那我们就进入正题吧。
* 计数(统计):
在我们日常编程中,计数操作是比较常用的。比如:我们要寻找一段list或数组中哪些元素出现次数比较多,用于统计的时候,怕不是我们会写出以下代码(当然,可能只有我会写成这样。。)
def count(datalist): #datalist为数据集, t为元组, cnt为list
data = list(set(datalist))
for i in data:
tmp = 0
for j in datalist:
if j==i:
tmp += 1
t = (i,tmp)
cnt.append(t)
print(cnt)
上述代码可以对一个乱序的数据列表进行统计其中各个元素出现的次数。虽然只有10行,但是我们使用line_profiler来对这段代码进行性能检测的时候(数据集使用randint来生成1000个0至9的数据数列),我们可以发现:
10行代码在测试性能过程中总耗时0.0171224s(不慢的对吧)。其中,count函数中第二个循环体占了总耗时的93.9%,可以说是耗时大户。第二层for及其if判断各执行了10000次,如果我们可以改进这段循环结构会不会对count函数带来性能的优化?来,我们试一下:
我对第二个循环做了调整,使用了列表推导式对元素进行统计计算。(可能你会有更好的方式来做着操作,若有,请在评论区分享一下你的操作吧。)在性能测试报告中可以看到,新的count函数只有8行,但是运行时间却达到了0.00174387s!对比前一个count函数的运行时间,新count函数提高了10倍。但是这种写法还是不好,因为每个元素的总数是需要通过计算数组长度来确定,而且 tp = [tmp+1 for j in datalist if j == i]还是占用了过多的时间。那么我们继续优化,这次我们使用字典结构加字典推导式方法。
# 骚操作1
def count(datalist):
data = list(set(datalist))
t = {i:datalist.count(i) for i in data}
print(t)
当我们使用字典结构再配合推导式时,在代码行数大幅减少的情况下,我们的第三种count函数的性能对比第二种count函数又提高了5倍,总共提高了50倍的性能。那么当我们还不是那么习惯使用推导式编程时,我们又有那些方法可以简化我们的操作呢?这里我们继续沿用上期所提到的标准库collections中Counter方法。
from collections import Counter
def count(datalist):
num_count = Counter(datalist)
print(num_count)
没错,直接一行搞定,而且运行时间也达到了0.0003736s与不使用标准库的count函数相当。
* 通过名称来访问元素
说到标准库collections,我们就再说一种使用名称来访问元素的方法。先抛个砖,有时我们创建的数据集中每个元素都是有特定的意义的比如:
stock_list = [['AAPL','10.30','11.90'],['YAHO','9.23','8.19'],['SINA','22.80','25.80']]
上面的二维数组stock_list的元素含义依次就是每个股票的股票名称,开盘价,以及收盘价。但是如果元素一旦很多的时候,我们要选取其中某一列的数据时,我们会比较头疼需要精确找到它的下标。现在我们可以使用collections中的namedtuple()方法为我们的数据元素进行命名。
# 骚操作2
>>>from collections import namedtuple
>>>stock_info = namedtuple('stock_info',['name','open','end'])
>>>stock_list = stock_info('AAPL','10.30','11.90')
>>>stock_list.name
AAPL
>>>name , *oped = stock_list
>>>oped
['10.30','11.90']
>>>stock_list = stock_list._replace(open = '0')
>>>stock_list
stock_info('AAPL','0','11.90')
这样我们就定义了一个命名元组,提供了一个类型名称以及相对应的字段:股票名,开盘价,收盘价。当我们需要寻找股票名时就可以写shock_list.name,这样是不是相对于shock_list[0]来得直观的多。而且namedtuple具有索引和分解功能。如果我们需要修改字段值,可以使用 ._replace()方法来实现。
* collection中的ChainMap()类
ChainMap类是用来将多个字典或映射合并成一个单独的映射,使用原始的字典结构。
>>>from collections import ChainMap
>>>f = {'a':1,'b':2}
>>>s = {'b':3,'c':3}
>>>e = ChainMap(f,s)
>>>print(e['b'])
2
ChainMap会先检查f的元素,如果f中不存在再去s中寻找,这样所以e['b']的值是f中的值而不是s中的。与单纯的字典update不同,如果使用update方法,e['b']就会等于3。
总结
今天讲述了3块内容:计数统计,命名数组和ChainMap的使用。在性能上,我们可以适当使用list comprehension和dictionary comprehension来简化我们的代码,提高性能。我们也可以使用namedtuple来直观地寻找(检索)信息。使用ChainMap来对多个字典合并到一个字典中,需要注意先后顺序对最终结果的影响。Finally,欢迎各位同好多提提建议,多多批评,谢谢。
参考文献
《Python Cookbook 中文版》第三版;
《Python性能分析与优化》
只求同好,无关浮名