Python的计数方式发展史

Python 解决问题的方式经常会随着时间的推移而发生改变。随着Python的演变,Python列表的计数方式也得到了发展。

对列表中各项目进行计数有各种各样的方法,接下来我们将通过观察每一种方法的代码风格,来分析这些技术的不同之处。至于它们的性能,我们以后再考虑。

我们需要了解一些 Python 的历史来理解这些不同的方法。幸运的是,我们可以用 Python 的 __future__ 模块,坐上时光机。现在让我们坐上德罗瑞恩(注:即系列电影《回到未来》中的时间机器),驶向1997年。

if 语句

现在是 1997 年 1 月,我们使用的是 Python 1.4。有一个颜色列表,我们很想要知道每一种颜色分别出现了多少次。我们来使用字典看看。

Python


7colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts={}

forcincolors:

ifcolor_counts.has_key(c):

color_counts[c]=color_counts[c]+1

else:

color_counts[c]=1

注:我们没有用+=,因为增量赋值语句在 Python2.0 版之后才被添加进去。我们也没有使用 c in color_counts 语句,因为这个语句到 Python2.2 版本才可以使用。

运行上述代码后,我们可以看到 color_counts 字典里包含了每一种颜色出现的次数。

Python


2>>>color_counts

{'brown':3,'yellow':2,'green':1,'black':1,'red':1}

这段代码很简单。通过循环遍历每一种颜色,检查每种颜色是否已经出现在字典中,如果还没有,把它添加到字典中。如果已经出现,那么把它的数量增一。

我们也可以按照下面的方式来写:

Python


6colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts={}

forcincolors:

ifnotcolor_counts.has_key(c):

color_counts[c]=0

color_counts[c]=color_counts[c]+1

对于稀疏列表(没有很多重复颜色的列表),这种方式也许会有一点点慢,因为在 for 循环里需要执行两条语句。但是我们不担心它的性能,我们考虑的是它的代码风格。深思熟虑之后,我们决定使用新版本的代码。

试试块语句

现在是 1997 年 1 月 2 日,我们仍然在使用 Python 1.4。某天的早晨,我们醒来后突然意识到,我们本来应该按照“先斩后奏” 的方式去实践(这种方式更符合 Python 的思想),但实际上,我们是按照“三思而后行”的想法在编写代码。现在按照“先斩后奏”的方式,把我们的代码重写成一个 try-except 的块语句:

Python


7colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts={}

forcincolors:

try:

color_counts[c]=color_counts[c]+1

exceptKeyError:

color_counts[c]=1

这段代码试着为每一种颜色的数量增一,但是如果这种颜色还没有出现在字典中,就会产生 KeyError 错误,那么该种颜色的数量就被初始化为 1。

get 方法

现在是 1998 年 1 月 1 日,Python 已经升级到 1.5 版本。我们决定用字典中的 get 方法来重写代码。

Python


4colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts={}

forcincolors:

color_counts[c]=color_counts.get(c,0)+1

这段代码循环遍历每一种颜色,每次循环从字典中获得当前颜色的计数,默认计数为零,然后计数加一,最后把新值赋给这个字典的键,也就是颜色。

这段代码很棒的是 for 循环里只有一行语句,但是我们并不确定这是不是更加符合 Python 的风格。我们觉得也许这次的改进显得太智能了,所以我们决定将这次的改进还原。

setdefault 方法

现在是 2001 年 1 月,我们已经在使用 Python 2.0 版。在 Python 2.0 版中,字典里新添加了 setdefault 的方法。于是我们决定用 setdefault 的方法和 += 的增量运算符来重写代码:

Python


5colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts={}

forcincolors:

color_counts.setdefault(c,0)

color_counts[c]+=1

在每次循环里,不管 setdefault 的方法是否会被用到都会被调用一次,但是这次的更改可读性更强,而且比之前的方法更符合Python的代码风格,因此我们决定保留这次更改。

fromkeys方法

现在是 2004 年 1 月 1 日,我们在使用 Python 2.3。听说字典里添加了一种新的 fromkeys 方法,这种方法可以从序列中获取键值来创建字典。我们用这个新方法来重写代码:

Python


4colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts=dict.fromkeys(colors,0)

forcincolors:

color_counts[c]+=1

这段代码用颜色作为键创建了一个新的字典,并且每种颜色的数量被初始化为0。 这种方式可以直接增加每一种颜色的数量,不需要担心这种颜色有没有被包含到字典中。这段代码没有检查和例外情况的处理,我们认为这是一次改进,因此我们保留这次更改。

集合

现在是 2005 年 1 月 1 日,我们在使用 Python2.4。我们意识到现在可以使用集合(在 Python 2.3 版时发布,内置在 Python 2.4 版)和链表(在 Python 2.0 版时发布)来解决计数问题。再思考之后,我们突然想起生成器表达式也只是在 Python 2.4 时才公布,因此我们决定使用生成器表达式中的一种来进行计数。

Python


2colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts=dict((c,colors.count(c))forcinset(colors))

注:我们没有使用字典解析,因为字典解析到 Python2.7 版才可以使用。

这个方法不错,只用了一行代码。但是这符合 Python 的风格吗?

我们记得在《Python 之禅》中,从 python 列表的邮件线程开始介绍,到 Python 发展到 2.2.1 版时结束。在我们的交互式解释器中输入 import this:

Python



22>>>importthis

TheZenofPython,byTimPeters

Beautifulisbetterthanugly.

Explicitisbetterthanimplicit.

Simpleisbetterthancomplex.

Complexisbetterthancomplicated.

Flatisbetterthannested.

Sparseisbetterthandense.

Readabilitycounts.

Specialcasesaren'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'reDutch.

Nowisbetterthannever.

Althoughneverisoftenbetterthan*right*now.

Iftheimplementationishardtoexplain,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'sdomoreofthose!

这段代码的复杂度是 O(n2),与复杂度为 O(n) 的代码相比,这段代码的美观度和可读性更差。这次的改变只是一次很有趣的实验,但是这种一行式的方法更不符合 Python 的风格。因此我们决定将这次改变恢复原状。

defaultdict 方法

现在是 2007 年 1 月 1 日,我们在使用 Python2.5。我们刚刚发现 defaultdict 已经出现在 Python 的标准库中了。我们可以使用 defaultdict 将字典中的值初始化为 0。我们用 defaultdict 来重写这段代码

Python


5fromcollectionsimportdefaultdict

colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts=defaultdict(int)

forcincolors:

color_counts[c]+=1

for 循环现在相当简单了。现在几乎符合 Python 的代码风格了。

在这段代码中,我们发现变量 color_counts 虽然跟字典不太一样,但是它继承了字典所有的映射功能。

Python


2>>>color_counts

defaultdict(,{'brown':3,'yellow':2,'green':1,'black':1,'red':1})

我们没有把 color_counts 这个类似于字典的对象转换成字典的格式,因为我们觉得以后的代码会更加的动态。

计数

现在是2011年1月,我们在使用Python 2.7。 我们了解到 defaultdict已经不是最符合 python 风格的计数方式了。在 Python 2.7 版的标准库中出现了一个 Counter 类,它可以为我们做所有的工作。

Python


3fromcollectionsimportCounter

colors=["brown","red","green","yellow","yellow","brown","brown","black"]

color_counts=Counter(colors)

还能变得更简单吗?这应该是最符合 Python 风格的代码了。

与 defaultdict 的方法一样,上面的代码返回一个类似字典的对象(实际上是字典的子类),这与我们的目的正好不谋而合,我们就使用这个方法了。

Python


2>>>color_counts

Counter({'brown':3,'yellow':2,'green':1,'black':1,'red':1})

深思之后:性能

注意一下,我们并没有关注这些计数方法的运行效率。这些方法中大部分复杂度为 O(n),但是由于 Python 的实现方式不同,运行时间也有所差异。

尽管性能不是我们关注的主要方面,我还是很关心这些方法在 CPython 3.5.0 版下的运行时间。根据颜色在列表中出现的频率去观察每一种方法的运行时间是一件很有趣的事情。

结论

根据《Python之禅》(《Zen of Python》)中的格言,“应该只有一种,并且最好是唯一的方法”。 这只是一种期望。实际上并不总是只有一种显著的方法。方法是可以根据时间,需求和专业水平而发生改变的。无论是学习任何一门语言,基础知识,就是基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路, 你的进步速度也会快很多,无论我们学习的目的是什么,不得不说Python真的是一门值得你付出时间去学习的优秀编程

语言。我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是304加上050最後799,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种

“Pythonic” 是相对的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容