有时,使用Python语言简洁优雅地解决问题的方法会随着时间的推移而发生变化。随着Python的发展,计算列表元素的方法也在发展。以统计一个元素在列表中出现的次数为例。我们可以编写许多不同的实现。在分析这些方法时,我们首先不关注性能,只考虑代码风格。要理解这些不同的实现,我们需要一些历史背景。幸运的是,我们生活在“未来”世界,拥有时光机。接下来,让我们一起乘坐时光机回到1997年,if语句1997年1月1日,我们使用的是Python1.4。现在有一个不同颜色的列表,我们想知道每种颜色在列表中出现的次数。让我们用字典做数学吧!colors=[“brown”,“red”,“green”,“yellow”,“yellow”,“brown”,“brown”,“black”]color_counts={}forcincolors:ifcolor_counts.has_key(c):color_counts[c]=color_counts[c]+1else:color_counts[c]=1注意:我们没有使用+=,因为增量赋值直到Python2.0才出现;另外,我们没有使用cincolor_counts惯用方法(idiom),因为它是在Python2.2中发明的。运行上面的代码后,我们会发现color_counts字典现在包含了列表中每种颜色的出现次数。>>>color_counts{'brown':3,'yellow':2,'green':1,'black':1,'red':1}上面的实现很简单。我们遍历每种颜色并检查该颜色是否在字典中。如果不是,则将颜色添加到字典中;如果是,增加这种颜色的数量。我们也可以将上面的代码重写为:colors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts={}forcincolors:ifnotcolor_counts.has_key(c):color_counts[c]=0color_counts[c]=color_counts[c]+1如果列表是稀疏的(即列表中不重复的颜色数量多),这段代码可能运行得有点慢。因为我们现在执行的是两条语句而不是一条。但是我们不关心性能问题,我们只关心编码风格。思前想后,我们决定采用新版本的代码。尝试代码块(CodeBlock)1997年1月2日,我们仍在使用Python1.4。当我们今天早上醒来时,我们才恍然大悟,我们的代码遵循的是“LookBeforeYouLeap”(先看后跳)原则,但实际上我们应该遵循“GetForgivenessiseasiertopermission”(EasiertoAskForgiveness,ThanPermission,也就是不检查,如果有问题会通过异常处理来处理),因为后者更简洁优雅。让我们用一个try-except块重构代码:colors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts={}forcincolors:try:color_counts[c]=color_counts[c]+1exceptKeyError:color_counts[c]=1现在我们的代码尝试增加每种颜色的计数。如果字典中没有颜色,则抛出KeyError,然后我们将该颜色的计数设置为1。get方法1998年1月1日,我们已升级到Python1.5。我们决定重构之前的代码,使用字典中新增的get方法。colors=[“brown”,“red”,“green”,“yellow”,“yellow”,“brown”,“brown”,“black”]color_counts={}forcincolors:color_counts[c]=color_counts.get(c,0)+1现在我们的代码遍历每种颜色,从字典中获取该颜色的当前计数值。如果没有这个计数值,颜色的计数值默认为0,然后在该值的基础上加1。***将字典中相应键的值设置为新的计数。将主要代码放在一行上很酷,但我们不完全确定它是否更简洁或更优雅。我们认为我们可能有点太聪明了,所以我们还是取消了这次重构。setdefault方法2001年1月1日,我们现在使用的是Python2.0。我们听说字典类型现在有一个setdefault方法,因此决定重构我们的代码以利用它。我们还决定使用新添加的+=增量赋值运算符。colors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts={}forcincolors:color_counts.setdefault(c,0)color_counts[c]+=1无论是否需要,我们都会通过循环每次调用setdefault方法。但这样做确实使代码看起来更具可读性。我们发现这种方法比以前的代码更加简洁和优雅,所以我们提交了这次修改。fromkeys方法2004年1月1日,我们使用的是Python2.3。我们听说字典增加了一个新的类方法fromkeys(类方法),它可以使用列表中的元素作为键来构建字典。我们重构代码以使用新方法:colors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts=dict.fromkeys(colors,0)forcincolors:color_counts[c]+=1这段代码创建了一个新的字典,以不同的颜色作为键,每个键的值默认设置为0。这样,当我们增加每个键的值时,我们不必担心它是否已经设置。我们也不需要在代码中进行检查或异常处理,这似乎是一种改进。我们决定修改代码。ComprehensionandSets2005年1月1日,我们现在使用Python2.4。我们发现我们可以使用集合(在Python2.3中发布,在2.4中内置类型)和列表理解(在Python2.0中发布)来解决计数问题。再想一想,发现Python2.4也发布了生成器表达式,最后决定不使用列表推导,而是使用生成器表达式。colors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts=dict((c,colors.count(c))为cinset(colors))注意:我们在这里没有使用字典理解,因为直到Python2.7才发明字典理解。运行成功,只有一行代码。但是这样的代码够简洁优雅吗?我们想起了Python之禅,这是一个Python编程指南,它起源于Python邮件列表,并悄悄地进入了Python2.2.1。我们在REPL(read-eval-printloop,interactiveinterpreter)界面输入importthis:>>>importthisTheZenofPython,byTimPeters美胜于丑。#Beautifulisbetterthanugly明确胜于含蓄。#清晰优于隐含简单优于复杂。#简单胜于复杂复杂胜于复杂。#复杂胜于难懂扁平胜于嵌套。#扁平优于嵌套稀疏优于密集。#Sparse优于Dense可读性很重要。#易读且有价值特殊情况不足以打破规则。#例外情况不能特别到违反规则的程度。虽然实用胜过纯粹。#尽管实用胜过纯粹。错误不应该悄无声息地过去。#永远的错误不应该在沉默中溜走,除非明确沉默。#除非明确沉默面对歧义,拒绝猜测的诱惑。#面对不确定性,应该有一种——最好只有一种——显而易见的方法来做到这一点。#应该有一种——而不是只有一种——显而易见的方法来做到这一点。尽管除非您是荷兰人,否则这种方式一开始可能并不明显。#也许这种方式一开始并不明显,除非你是荷兰人现在总比没有好。#Nowdoitisbetterthannotdoit虽然从来没有比现在更好。#虽然不做通常比马上做要好Iftheimplement很难解释,这是个坏主意。#如果实现很容易解释,这可能是个好主意。#如果实现很容易解释,这可能是个好主意。#如果实现很容易解释,那可能是个坏主意社区我们的代码变得更复杂了,时间复杂度从O(n)增加到O(n2);它也变得更加丑陋和可读性差。我们这样改是一个有趣的尝试,但是一行代码的实现形式没有我们之前的方法那么简洁优雅。我们已经决定撤消修改。defaultdict方法2007年1月1日,我们使用的是Python2.5。我们刚刚发现defaultdict已添加到标准库中。这样,我们就可以将字典的默认键值设置为0。让我们重构代码以使用defaultdict:fromcollectionsimportdefaultdictcolors=["brown","red","green","yellow","yellow","brown","brown","black"]color_counts=defaultdict(int)forcincolors:color_counts[c]+=1for循环现在变得如此简单!这样绝对更简洁优雅。我们发现color_counts变量的行为现在有点不同,但它确实继承了字典的特性,支持所有相同的映射函数。>>>color_countsdefaultdict(
