点击上方“
程序员共读
”,选择“置顶公众号”
关键时刻,第一时间送达!
译序
如果说优雅也有缺点的话,那就是你需要艰巨的工作才能得到它,需要良好的教育才能欣赏它。
—— Edsger Wybe Dijkstra
在Python社区文化的浇灌下,演化出了一种独特的代码风格,去指导如何正确地使用Python,这就是常说的pythonic。一般说地道(idiomatic)的python代码,就是指这份代码很pythonic。Python的语法和标准库设计,处处契合着pythonic的思想。而且Python社区十分注重编码风格一的一致性,他们极力推行和处处实践着pythonic。所以经常能看到基于某份代码P vs NP (pythonic vs non-pythonic)的讨论。pythonic的代码简练,明确,优雅,绝大部分时候执行效率高。阅读pythonic的代码能体会到“代码是写给人看的,只是顺便让机器能运行”畅快。
然而什么是pythonic,就像什么是地道的汉语一样,切实存在但标准模糊。import this可以看到Tim Peters提出的Python之禅,它提供了指导思想。许多初学者都看过它,深深赞同它的理念,但是实践起来又无从下手。PEP 8给出的不过是编码规范,对于实践pythonic还远远不够。如果你正被如何写出pythonic的代码而困扰,或许这份笔记能给你帮助。
Raymond Hettinger是Python核心开发者,本文提到的许多特性都是他开发的。同时他也是Python社区热忱的布道师,不遗余力地传授pythonic之道。这篇文章是网友Jeff Paine整理的他在2013年美国的PyCon的演讲的笔记。
术语澄清:本文所说的集合全都指collection,而不是set。
以下是正文。
本文是Raymond Hettinger在2013年美国PyCon演讲的笔记(视频, 幻灯片)。
示例代码和引用的语录都来自Raymond的演讲。这是我按我的理解整理出来的,希望你们理解起来跟我一样顺畅!
遍历一个范围内的数字
foriin[0,1,2,3,4,5]:printi **2foriinrange(6):printi **2
更好的方法
foriinxrange(6):printi **2
xrange会返回一个迭代器,用来一次一个值地遍历一个范围。这种方式会比range更省内存。xrange在Python 3中已经改名为range。
遍历一个集合
colors=['red','green','blue','yellow']foriinrange(len(colors)):printcolors[i]
更好的方法
forcolorincolors:printcolor
反向遍历
colors=['red','green','blue','yellow']foriinrange(len(colors)-1,-1,-1):printcolors[i]
更好的方法
forcolorinreversed(colors):printcolor
遍历一个集合及其下标
colors=['red','green','blue','yellow']foriinrange(len(colors)):printi,'--->',colors[i]
更好的方法
fori,colorinenumerate(colors):printi,'--->',color
这种写法效率高,优雅,而且帮你省去亲自创建和自增下标。
当你发现你在操作集合的下标时,你很有可能在做错事。
遍历两个集合
names=['raymond','rachel','matthew']colors=['red','green','blue','yellow']n=min(len(names),len(colors))foriinrange(n):printnames[i],'--->',colors[i]forname,colorinzip(names,colors):printname,'--->',color
更好的方法
forname,colorinizip(names,colors):printname,'--->',color
zip在内存中生成一个新的列表,需要更多的内存。izip比zip效率更高。
注意:在Python 3中,izip改名为zip,并替换了原来的zip成为内置函数。
有序地遍历
colors=['red','green','blue','yellow']# 正序forcolorinsorted(colors):printcolors# 倒序forcolorinsorted(colors,reverse=True):printcolors
自定义排序顺序
colors=['red','green','blue','yellow']def compare_length(c1,c2):iflen(c1)iflen(c1)>len(c2):return1return0print sorted(colors,cmp=compare_length)
更好的方法
print sorted(colors, key=len)
第一种方法效率低而且写起来很不爽。另外,Python 3已经不支持比较函数了。
调用一个函数直到遇到标记值
blocks=[]whileTrue:block=f.read(32)ifblock=='':breakblocks.append(block)
更好的方法
blocks=[]forblockiniter(partial(f.read,32),''):blocks.append(block)
iter接受两个参数。第一个是你反复调用的函数,第二个是标记值。
译注:这个例子里不太能看出来方法二的优势,甚至觉得partial让代码可读性更差了。方法二的优势在于iter的返回值是个迭代器,迭代器能用在各种地方,set,sorted,min,max,heapq,sum……
在循环内识别多个退出点
def find(seq,target):found=Falsefori,valueinenumerate(seq):ifvalue==target:found=Truebreakifnotfound:return-1returni
更好的方法
def find(seq,target):fori,valueinenumerate(seq):ifvalue==target:breakelse:return-1returni
for执行完所有的循环后就会执行else。
译注:刚了解for-else语法时会困惑,什么情况下会执行到else里。有两种方法去理解else。传统的方法是把for看作if,当for后面的条件为False时执行else。其实条件为False时,就是for循环没被break出去,把所有循环都跑完的时候。所以另一种方法就是把else记成nobreak,当for没有被break,那么循环结束时会进入到else。
遍历字典的key
d={'matthew':'blue','rachel':'green','raymond':'red'}forkind:printkforkind.keys():ifk.startswith('r'):deld[k]
什么时候应该使用第二种而不是第一种方法?当你需要修改字典的时候。
如果你在迭代一个东西的时候修改它,那就是在冒天下之大不韪,接下来发生什么都活该。
d.keys()把字典里所有的key都复制到一个列表里。然后你就可以修改字典了。
注意:如果在Python 3里迭代一个字典你得显示地写:list(d.keys()),因为d.keys()返回的是一个“字典视图”(一个提供字典key的动态视图的迭代器)。详情请看文档。
遍历一个字典的key和value
# 并不快,每次必须要重新哈希并做一次查找forkind:printk,'--->',d[k]# 产生一个很大的列表fork,vind.items():printk,'--->',v
更好的方法
fork,vind.iteritems():printk,'--->',v
iteritems()更好是因为它返回了一个迭代器。
注意:Python 3已经没有iteritems()了,items()的行为和iteritems()很接近。详情请看文档。
用key-value对构建字典
names=['raymond','rachel','matthew']colors=['red','green','blue']d=dict(izip(names,colors))# {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
Python 3: d = dict(zip(names, colors))
用字典计数
colors=['red','green','red','blue','green','red']# 简单,基本的计数方法。适合初学者起步时学习。d={}forcolorincolors:ifcolornotind:d[color]=0d[color]+=1# {'blue': 1, 'green': 2, 'red': 3}
更好的方法
d={}forcolorincolors:d[color]=d.get(color,0)+1# 稍微潮点的方法,但有些坑需要注意,适合熟练的老手。d=defaultdict(int)forcolorincolors:d[color]+=1
用字典分组 — 第I部分和第II部分
names=['raymond','rachel','matthew','roger','betty','melissa','judith','charlie']# 在这个例子,我们按name的长度分组d={}fornameinnames:key=len(name)ifkeynotind:d[key]=[]d[key].append(name)# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}d={}fornameinnames:key=len(name)d.setdefault(key,[]).append(name)
更好的方法
d=defaultdict(list)fornameinnames:key=len(name)d[key].append(name)
字典的popitem()是原子的吗?
d={'matthew':'blue','rachel':'green','raymond':'red'}whiled:key,value=d.popitem()printkey,'-->',value
popitem是原子的,所以多线程的时候没必要用锁包着它。
连接字典
defaults={'color':'red','user':'guest'}parser=argparse.ArgumentParser()parser.add_argument('-u','--user')parser.add_argument('-c','--color')namespace=parser.parse_args([])command_line_args={k:vfork,vinvars(namespace).items()ifv}# 下面是通常的作法,默认使用第一个字典,接着用环境变量覆盖它,最后用命令行参数覆盖它。# 然而不幸的是,这种方法拷贝数据太疯狂d=defaults.copy()d.update(os.environ)d.update(command_line_args)
更好的方法
d = ChainMap(command_line_args, os.environ, defaults)
ChainMap在Python 3中加入。高效而优雅。