在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 ** 2

foriinrange(6):

printi ** 2

更好的方法

foriinxrange(6):

printi ** 2

xrange会返回一个迭代器,用来一次一个值地遍历一个范围。这种方式会比range更省内存。xrange在Python 3中已经改名为range。

遍历一个集合

colors= ['red','green','blue','yellow']

foriinrange(len(colors)):

print colors[i]

更好的方法

forcolor incolors:

print color

反向遍历

colors= ['red','green','blue','yellow']

foriinrange(len(colors)-1,-1,-1):

print colors[i]

更好的方法

forcolor inreversed(colors):

print color

遍历一个集合及其下标

colors= ['red','green','blue','yellow']

foriinrange(len(colors)):

printi,'--->',colors[i]

更好的方法

fori,color inenumerate(colors):

printi,'--->',color

这种写法效率高,优雅,而且帮你省去亲自创建和自增下标。

当你发现你在操作集合的下标时,你很有可能在做错事。

遍历两个集合

names= ['raymond','rachel','matthew']

colors= ['red','green','blue','yellow']

n= min(len(names),len(colors))

foriinrange(n):

print names[i],'--->',colors[i]

forname,color inzip(names,colors):

print name,'--->',color

更好的方法

forname,color inizip(names,colors):

print name,'--->',color

zip在内存中生成一个新的列表,需要更多的内存。izip比zip效率更高。

注意:在Python 3中,izip改名为zip,并替换了原来的zip成为内置函数。

有序地遍历

colors= ['red','green','blue','yellow']

# 正序

forcolor insorted(colors):

print colors

# 倒序

forcolor insorted(colors,reverse=True):

print colors

自定义排序顺序

colors= ['red','green','blue','yellow']

def compare_length(c1,c2):

iflen(c1)< len(c2): return-1

iflen(c1)> len(c2): return1

return0

print sorted(colors,cmp=compare_length)

更好的方法

print sorted(colors, key=len)

第一种方法效率低而且写起来很不爽。另外,Python 3已经不支持比较函数了。

调用一个函数直到遇到标记值

blocks= []

whileTrue:

block= f.read(32)

ifblock== '':

break

blocks.append(block)

更好的方法

blocks= []

forblock initer(partial(f.read,32),''):

blocks.append(block)

iter接受两个参数。第一个是你反复调用的函数,第二个是标记值。

译注:这个例子里不太能看出来方法二的优势,甚至觉得partial让代码可读性更差了。方法二的优势在于iter的返回值是个迭代器,迭代器能用在各种地方,set,sorted,min,max,heapq,sum……

在循环内识别多个退出点

def find(seq,target):

found= False

fori,value inenumerate(seq):

ifvalue== target:

found= True

break

ifnotfound:

return-1

returni

更好的方法

def find(seq,target):

fori,value inenumerate(seq):

ifvalue== target:

break

else:

return-1

returni

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:

printk

forkind.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= {}

forcolor incolors:

ifcolor notind:

d[color]= 0

d[color]+= 1

# {'blue': 1, 'green': 2, 'red': 3}

更好的方法

d= {}

forcolor incolors:

d[color]= d.get(color,0)+ 1

# 稍微潮点的方法,但有些坑需要注意,适合熟练的老手。

d= defaultdict(int)

forcolor incolors:

d[color]+= 1

用字典分组 — 第I部分和第II部分

names= ['raymond','rachel','matthew','roger',

'betty','melissa','judith','charlie']

# 在这个例子,我们按name的长度分组

d= {}

forname innames:

key= len(name)

ifkey notind:

d[key]= []

d[key].append(name)

# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}

d= {}

forname innames:

key= len(name)

d.setdefault(key,[]).append(name)

更好的方法

d= defaultdict(list)

forname innames:

key= len(name)

d[key].append(name)

字典的popitem()是原子的吗?

d= {'matthew': 'blue','rachel': 'green','raymond': 'red'}

whiled:

key,value= d.popitem()

print key,'-->',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中加入。高效而优雅。

提高可读性

位置参数和下标很漂亮

但关键字和名称更好

第一种方法对计算机来说很便利

第二种方法和人类思考方式一致

用关键字参数提高函数调用的可读性

twitter_search('@obama', False, 20, True)

更好的方法

twitter_search('@obama', retweets=False, numtweets=20, popular=True)

第二种方法稍微(微秒级)慢一点,但为了代码的可读性和开发时间,值得。

用namedtuple提高多个返回值的可读性

# 老的testmod返回值

doctest.testmod()

# (0, 4)

# 测试结果是好是坏?你看不出来,因为返回值不清晰。

更好的方法

# 新的testmod返回值, 一个namedtuple

doctest.testmod()

# TestResults(failed=0, attempted=4)

namedtuple是tuple的子类,所以仍适用正常的元组操作,但它更友好。

创建一个nametuple

TestResults = namedTuple('TestResults', ['failed', 'attempted'])

unpack序列

p= 'Raymond','Hettinger',0x30,'python@example.com'

# 其它语言的常用方法/习惯

fname= p[0]

lname= p[1]

age= p[2]

email= p[3]

更好的方法

fname, lname, age, email = p

第二种方法用了unpack元组,更快,可读性更好。

更新多个变量的状态

def fibonacci(n):

x= 0

y= 1

foriinrange(n):

printx

t= y

y= x+ y

x= t

更好的方法

def fibonacci(n):

x,y= 0,1

foriinrange(n):

printx

x,y= y,x+ y

第一种方法的问题

x和y是状态,状态应该在一次操作中更新,分几行的话状态会互相对不上,这经常是bug的源头。

操作有顺序要求

太底层太细节

第二种方法抽象层级更高,没有操作顺序出错的风险而且更效率更高。

同时状态更新

tmp_x= x+ dx *t

tmp_y= y+ dy *t

tmp_dx= influence(m,x,y,dx,dy,partial='x')

tmp_dy= influence(m,x,y,dx,dy,partial='y')

x= tmp_x

y= tmp_y

dx= tmp_dx

dy= tmp_dy

更好的方法

x,y,dx,dy= (x+ dx *t,

y+ dy *t,

influence(m,x,y,dx,dy,partial='x'),

influence(m,x,y,dx,dy,partial='y'))

效率

优化的基本原则

除非必要,别无故移动数据

稍微注意一下用线性的操作取代O(n**2)的操作

总的来说,不要无故移动数据

连接字符串

names= ['raymond','rachel','matthew','roger',

'betty','melissa','judith','charlie']

s= names[0]

forname innames[1:]:

s+= ', '+ name

prints

更好的方法

print ', '.join(names)

更新序列

names= ['raymond','rachel','matthew','roger',

'betty','melissa','judith','charlie']

del names[0]

# 下面的代码标志着你用错了数据结构

names.pop(0)

names.insert(0,'mark')

更好的方法

names= deque(['raymond','rachel','matthew','roger',

'betty','melissa','judith','charlie'])

# 用deque更有效率

del names[0]

names.popleft()

names.appendleft('mark')

装饰器和上下文管理

用于把业务和管理的逻辑分开

分解代码和提高代码重用性的干净优雅的好工具

起个好名字很关键

记住蜘蛛侠的格言:能力越大,责任越大

使用装饰器分离出管理逻辑

# 混着业务和管理逻辑,无法重用

def web_lookup(url,saved={}):

ifurl insaved:

returnsaved[url]

page= urllib.urlopen(url).read()

saved[url]= page

returnpage

更好的方法

@cache

def web_lookup(url):

returnurllib.urlopen(url).read()

注意:Python 3.2开始加入了functools.lru_cache解决这个问题。

分离临时上下文

# 保存旧的,创建新的

old_context= getcontext().copy()

getcontext().prec= 50

print Decimal(355)/ Decimal(113)

setcontext(old_context)

更好的方法

with localcontext(Context(prec=50)):

print Decimal(355)/ Decimal(113)

译注:示例代码在使用标准库decimal,这个库已经实现好了localcontext。

如何打开关闭文件

f= open('data.txt')

try:

data= f.read()

finally:

f.close()

更好的方法

with open('data.txt')asf:

data= f.read()

如何使用锁

# 创建锁

lock= threading.Lock()

# 使用锁的老方法

lock.acquire()

try:

print'Critical section 1'

print'Critical section 2'

finally:

lock.release()

更好的方法

# 使用锁的新方法

with lock:

print'Critical section 1'

print'Critical section 2'

分离出临时的上下文

try:

os.remove('somefile.tmp')

except OSError:

pass

更好的方法

with ignored(OSError):

os.remove('somefile.tmp')

ignored是Python 3.4加入的, 文档。

注意:ignored 实际上在标准库叫suppress(译注:contextlib.supress).

试试创建你自己的ignored上下文管理器。

@contextmanager

def ignored(*exceptions):

try:

yield

except exceptions:

pass

把它放在你的工具目录,你也可以忽略异常

译注:contextmanager在标准库contextlib中,通过装饰生成器函数,省去用__enter__和__exit__写上下文管理器。详情请看文档。

分离临时上下文

# 临时把标准输出重定向到一个文件,然后再恢复正常

with open('help.txt','w')asf:

oldstdout= sys.stdout

sys.stdout= f

try:

help(pow)

finally:

sys.stdout= oldstdout

更好的写法

with open('help.txt','w')asf:

with redirect_stdout(f):

help(pow)

redirect_stdout在Python 3.4加入(译注:contextlib.redirect_stdout), bug反馈。

实现你自己的redirect_stdout上下文管理器。

@contextmanager

def redirect_stdout(fileobj):

oldstdout= sys.stdout

sys.stdout= fileobj

try:

yield fieldobj

finally:

sys.stdout= oldstdout

简洁的单句表达

两个冲突的原则:

一行不要有太多逻辑

不要把单一的想法拆分成多个部分

Raymond的原则:

一行代码的逻辑等价于一句自然语言

列表解析和生成器

result= []

foriinrange(10):

s= i ** 2

result.append(s)

print sum(result)

更好的方法

print sum(i**2 for i in xrange(10))

第一种方法说的是你在做什么,第二种方法说的是你想要什么。

来源:www.lightxue.com/transforming-code-into-beautiful-idiomatic-python 侵删。

python代码大全p-如何写出优雅又地道的Python代码?【转载】相关推荐

  1. python怎么写出来的_如何写出优雅又地道的Python代码?【转载】

    在Python社区文化的浇灌下,演化出了一种独特的代码风格,去指导如何正确地使用Python,这就是常说的pythonic.一般说地道(idiomatic)的python代码,就是指这份代码很pyth ...

  2. c语言迷宫闯关游戏大全,C语言写出的迷宫闯关游戏代码.doc

    C语言写出的迷宫闯关游戏代码 C语言写出的迷宫闯关游戏代码: #include #include #define LEFT 75 #define RIGHT 77 #define UPPER 72 # ...

  3. 如何写出同事看不懂的Java代码?

    壹.瞒天过海 我打赌你肯定想不到,有人居然会在注释里下了毒.看看下面的代码,简单到main方法中只有一行注释. public static void main(String[] args) {// \ ...

  4. python如何写代码_如何写出优雅的Python代码?

    有时候你会看到很Cool的Python代码,你惊讶于它的简洁,它的优雅,你不由自主地赞叹:竟然还能这样写.其实,这些优雅的代码都要归功于Python的特性,只要你能掌握这些Pythonic的技巧,你一 ...

  5. 优雅写mysql_如何写出优雅的代码?

    我 18 年在 PyCon 做过一次类似的分享 ,目的是如何写出优雅的代码.希望能部分回答这个问题,也希望与大家多多讨论.<聊聊编程原则>​zhuanlan.zhihu.com 要成为合格 ...

  6. 如何写出优雅的 Golang 代码

    Go 语言是一门简单.易学的编程语言,对于有编程背景的工程师来说,学习 Go 语言并写出能够运行的代码并不是一件困难的事情,对于之前有过其他语言经验的开发者来说,写什么语言都像自己学过的语言其实是有问 ...

  7. 如何写出优雅的 Go 语言代码

    如何写出优雅的 Go 语言代码 查看全文 http://www.taodudu.cc/news/show-3196428.html 相关文章: 写日志的那些事儿 写有价值的单元测试 腾讯私有云MySQ ...

  8. 程序员如何写出优雅的代码?

    作者 | 老峰 责编 | 郭芮 一直以来,关于"代码规范"的话题都备受关注,业界甚至有很多流传甚广的段子不断调侃之.既然代码规范能引起这么大的共鸣,那么今天我们谈谈一个程序员的自我 ...

  9. 谈代码整洁之道,如何写出优雅的代码

    最近为了制定团队的代码规范,拜读了鲍勃大叔的<代码整洁之道>,读完之后,在如何写出整洁优雅的高质量代码方面有很大的启发.我认为<代码整洁之道>是我们提高编程能力和自我修养必读之 ...

最新文章

  1. 数据结构(06)— 线性循环链表实战
  2. 科研文献|结肠直肠癌早期检测中跨群体微生物标记物的鉴定
  3. [CF 392 Div.1 D]Three Arrays
  4. windows下编译lua源码
  5. MySQL分组查询—添加筛选的总结
  6. AVS264_FAQ集锦
  7. [react] react与angular、vue有什么区别?
  8. 英文书《用unreal来学习c++》_用机器学习来概括《哈利波特》,四句话总结一场戏...
  9. 翻译【ElasticSearch Server】第一章:开始使用ElasticSearch集群(2)
  10. c++堆栈溢出怎么解决_StackOverFlowError 常见原因及解决方法
  11. 数组中的两个常见异常
  12. 国际C语言混乱代码大赛
  13. html基础之背景属性
  14. spring mybatis使用时报 java.lang.AbstractMethodError
  15. dcp9030cdn定影_兄弟DCP-9030CDN打印驱动下载|兄弟Brother DCP-9030CDN一体打印机驱动官方下载 - 维维软件园...
  16. EXCEL 宏 拆分数据表 VBA 视频教程下载
  17. 内录音频(Windows系统)
  18. 笔记 c语言99乘法表
  19. samba端口号修改_samba 端口号(samb端口号)
  20. 计算机历史记录无法删除,win10时间线灰色浏览记录删不掉怎么回事_win10时间线历史灰色无法删除的解决教程-win7之家...

热门文章

  1. AC-Tek Sidewinder v7.2.2 输送机设计+IDEA StatiCa v9.1.31.50722 钢结构混凝土结构件设计...
  2. thymeleaf模板的使用(转)
  3. Storm介绍及安装部署
  4. timeout connect 10000 # default 10 second time out if a backend is not found
  5. leetcode[161] One Edit Distance
  6. 收购创业公司如何评估价值
  7. Python 中的用户自定义类型
  8. BootStrap入门教程 (四)
  9. 又遇到问题:wrong ELF class: ELFCLASS32 in Unknown on line
  10. Ferderweisser