文中如对专业术语的翻译有误,请Python高手指正(Pythonistas),谢谢!另外,原文中的Pythonic一词,大意指符合Python语言规范、特性和数据结构的编码方式,蕴涵较为丰富,为了行文更顺畅、容易理解,此文暂时用简洁、优雅代替。-- <cite>EarlGrey@编程派</cite>html
原文连接:Trey Hunner,发布于11月9日。
译文连接:编程派node
有时候,利用Python语言简洁、优雅地解决问题的方法,会随着时间变化。随着Python不断进化,统计列表元素数量的方法也在改变。python
以计算元素在列表中出现的次数为例,咱们能够编写出许多不一样的实现方法。在分析这些方法时,咱们先不关注性能,只考虑代码风格。git
要理解这些不一样的实现方式,咱们得先知道一些历史背景。幸运的是,咱们生活在"__future__"世界,拥有一台时间机器。接下来,咱们一块儿坐上时光机,回到1997年吧。github
if
语句1997年1月1日,咱们使用的是Python 1.4。如今有一个不一样颜色组成的列表,咱们想知道列表里每种颜色出现的次数。咱们用字典来计算吧!express
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: if color_counts.has_key(c): color_counts[c] = color_counts[c] + 1 else: color_counts[c] = 1
注意:咱们没有使用+=
,由于增量赋值直到Python 2.0才出现;另外,咱们也没有使用c in color_counts
这个惯用法(idiom),由于这也是Python 2.2中才发明的,编程
运行上述代码以后,咱们会发现color_counts
字典里,如今包含了列表中每种颜色的出现次数。数据结构
color_counts {'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1}
上面的实现很简单。咱们遍历了每一种颜色,并判断该颜色是否在字典中。若是不在,就在字典加入该颜色;若是在,就增长这种颜色的计数。less
咱们还能够把上面的代码改写为:ide
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: if not color_counts.has_key(c): color_counts[c] = 0 color_counts[c] = color_counts[c] + 1
若是列表稀疏度高(即列表中不重复的颜色数量不少),这段代码可能运行的会有点慢。由于咱们如今要执行两个语句,而不是一个。可是咱们不关心性能问题,咱们只关注编码风格。通过思考,咱们决定采用新版的代码。
try
代码块(Code Block)1997年1月2日,咱们使用的仍是Python 1.4。今早醒来的时候,咱们忽然意识到:咱们的代码遵循的是“三思然后行”(Look Before You Leap,即事先检查每一种可能出现的状况)原则,但实际上咱们应该按照“得到谅解比得到许可容易”(Easier to Ask Forgiveness, Than Permission,即不检查,出了问题由异常处理来处理)的原则进行编程,由于后者更加简洁、优雅。咱们用try-except
代码块来重构下代码吧:
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: try: color_counts[c] = color_counts[c] + 1 except KeyError: color_counts[c] = 1
如今,咱们的代码尝试增长每种颜色的计数。若是某颜色不在字典里,那么就会抛出KeyError,咱们随之将该颜色的计数设置为1。
get
方法1998年1月1日,咱们已经升级到了Python 1.5。咱们决定重构以前的代码,使用字典中新增的get
方法。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: color_counts[c] = color_counts.get(c, 0) + 1
如今,咱们的代码会遍历每种颜色,从字典中获取该颜色的当前计数值。若是没有这个计数值,则该颜色的计数值默认为0,而后在数值的基础上加1。最后将字典中相应键的值设置为新的计数。
把主要代码都写在一行里,感受很酷,可是咱们不敢彻底确定这种作法更加简洁、优雅。咱们以为可能有点太聪敏了,因此仍是撤销了此次的重构。
setdefault
方法2001年1月1日,咱们如今使用的是Python 2.0。咱们据说字典类型如今有一个setdefault
方法,决定利用它重构咱们的代码。咱们还决定使用新增长的+=
增量赋值运算符。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: color_counts.setdefault(c, 0) color_counts[c] += 1
不管是否须要,咱们在每一次循环时都会调用setdefault
方法。但这样作,的确会让代码看上去可读性更高。咱们发现这种方法比以前的代码更加简洁、优雅,因此提交了这次修改。
fromkeys
方法2004年1月1日,咱们使用的是Python 2.3。咱们据说字典新增了一个叫fromkeys
的类方法(class method),能够利用列表中的元素做为键来构建字典。咱们使用新方法重构了代码:
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = dict.fromkeys(colors, 0) for c in colors: color_counts[c] += 1
这段代码将不一样的颜色做为键,建立了一个新的字典,每一个键的值被默认设置为0。这样,咱们增长每一个键的值时,就不用担忧是否已经进行了设置。咱们也不须要在代码中进行检查或异常处理了,这看上去是个改进。咱们决定就这样修改代码。
2005年1月1日,咱们如今用的是Python 2.4。咱们发现能够利用集合(Python 2.3中发布,2.4版成为内置类型)与列表推导式(Python 2.0中发布)来解决计数问题。进一步思考以后,我想起来Python 2.4中还发布了生成器表达式(generator expressions),咱们最后决定不用列表推导式,而是采用生成器表达式。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = dict((c, colors.count(c)) for c in set(colors))
注意:咱们这里使用的不是字典推导式,由于字典推导式直到Python 2.7才被发明。
运行成功了,并且只有一行代码。可是这种代码够简洁、优雅吗 ?
咱们想起了Python之禅(Zen of Python),这个Python编程指导原则起源于一个Python邮件列表,并悄悄地收进了Python 2.2.1版本中。咱们在REPL(read-eval-print loop,交互式解释器)界面中输入import this
:
import this The Zen of Python, by Tim Peters Beautiful is better than ugly. # 优美赛过丑陋 Explicit is better than implicit. # 明确赛过含蓄 Simple is better than complex. # 简单赛过复杂 Complex is better than complicated. # 复杂赛过难懂 Flat is better than nested. # 扁平赛过嵌套 Sparse is better than dense. # 稀疏赛过密集 Readability counts. # 易读亦有价 Special cases aren't special enough to break the rules. # 特例也不能特殊到打破规则 Although practicality beats purity. # 尽管实用会击败纯洁 Errors should never pass silently. # 错误永远不该默默地溜掉 Unless explicitly silenced. # 除非明确地使其沉默 In the face of ambiguity, refuse the temptation to guess. # 面对着不肯定,要拒绝猜想的诱惑 There should be one-- and preferably only one --obvious way to do it. # 应该有一个–宁可只有一个–明显的实现方法 Although that way may not be obvious at first unless you're Dutch. # 也许这个方法开始不是很明显,除非你是荷兰人 Now is better than never. #如今作也要赛过不去作 Although never is often better than right now. # 尽管不作一般好过马上作 If the implementation is hard to explain, it's a bad idea. # 若是实现很难解释,那它就是一个坏想法 If the implementation is easy to explain, it may be a good idea. # 若是实现容易解释,那它可能就是一个好想法 Namespaces are one honking great idea -- let's do more of those! # 命名空间是一个响亮的出色想法–就让咱们多用用它们
译者注:Python之禅的翻译版本不少,这里选用的译文出自啄木鸟社区。
咱们的代码变得更复杂,时间复杂度从O(n)增长到了O(n2);还变的更丑,可读性更差了。咱们那样改,是一次有趣的尝试,可是一行代码的实现形式,没有咱们以前的方法简洁、优雅。咱们最后仍是决定撤销修改。
defaultdict
方法2007年1月1日,咱们使用的是Python 2.5。咱们刚发现,defaultdict
已经被加入标准库。这样,咱们就能够把字典的默认键值设置为0了。让咱们使用defaultdict
重构代码:
from collections import defaultdict colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = defaultdict(int) for c in colors: color_counts[c] += 1
那个for
循环如今变得真简单!这样确定是更加简洁、优雅了。
咱们发现,color_counts
这个变量的行为如今有点不一样,可是它的确继承了字典的特性,支持全部相同的映射功能。
color_counts defaultdict(<type 'int'>, {'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1})
咱们在这里没有把color_counts
转换成字典,而是假设其余的代码也使用鸭子类型(duck typing, Python中动态类型的一种,这里的意思是:其余代码会将color_counts
视做字典类型),再也不改动这个相似字典的对象。
Counter
类2011年1月1日,咱们使用的是Python 2.7。别人告诉咱们,以前使用defaultdict
编写的代码,再也不是统计颜色出现次数最简洁、优雅的方法了。Python 2.7中新引入了一个Counter
类,能够彻底解决咱们的问题。
from collections import Counter colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = Counter(colors)
还有比这更简单的方法吗?这个必定是最简洁、优雅的实现了。
与defaultdict
同样,Counter
类返回的也是一个相似字典的对象(实际是字典的一个子类)。这对知足咱们的需求来讲足够了,因此咱们就这么干了。
color_counts Counter({'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1})
请注意,在编写这些实现方式时,咱们都没有关注效率问题。大部分方法的时间复杂度相同(O(n)),可是不一样的Python语言实现形式(如CPython, PyPy,或者Jython)下,运行时间会有差别。
尽管性能不是咱们的主要关注点,我仍是在CPython 3.5.0的实现下测试了运行时间。从中,你会发现一个有趣的现象:随着列表中颜色元素的密度(即相同元素的数量)变化,每一种实现方法的相对效率也会不一样。
根据Python之禅,“ 应该有一个——宁可只有一个明显的实现方法”。这句话所说的状态值得追求,但事实是,并不老是只有一种明显的方法。这个“明显”的方法会随着时间、需求和专业水平,不断地变化。
“简洁、优雅”(即Pythonic)也是一个相对的概念。