让你的 Python 代码优雅又地道

翻译:Light Xue 来源:

译序

如果说优雅也有缺点的话,那就是你需要艰巨的工作才能得到它,需要良好的教育才能欣赏它。

—— 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的演讲。这是我按我的理解整理出来的,希望你们理解起来跟我一样顺畅!

遍历一个范围内的数字

更好的方法

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

遍历一个集合

更好的方法

反向遍历

更好的方法

遍历一个集合及其下标

更好的方法

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

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

遍历两个集合

更好的方法

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

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

有序地遍历

自定义排序顺序

更好的方法

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

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

更好的方法

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

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

在循环内识别多个退出点

更好的方法

for执行完所有的循环后就会执行else。

译注:刚了解for-else语法时会困惑,什么情况下会执行到else里。有两种方法去理解else。传统的方法是把for看作if,当for后面的条件为False时执行else。其实条件为False时,就是for循环没被break出去,把所有循环都跑完的时候。所以另一种方法就是把else记成nobreak,当for没有被break,那么循环结束时会进入到else。

遍历字典的key

什么时候应该使用第二种而不是第一种方法?当你需要修改字典的时候。

如果你在迭代一个东西的时候修改它,那就是在冒天下之大不韪,接下来发生什么都活该。

d.keys()把字典里所有的key都复制到一个列表里。然后你就可以修改字典了。

注意:如果在Python 3里迭代一个字典你得显示地写:list(d.keys()),因为d.keys()返回的是一个“字典视图”(一个提供字典key的动态视图的迭代器)。详情请看文档。

遍历一个字典的key和value

更好的方法

iteritems()更好是因为它返回了一个迭代器。

注意:Python 3已经没有iteritems()了,items()的行为和iteritems()很接近。

用key-value对构建字典

Python 3: d = dict(zip(names, colors))

用字典计数

更好的方法

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

更好的方法

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

popitem是原子的,所以多线程的时候没必要用锁包着它。

连接字典

更好的方法

ChainMap在Python 3中加入。高效而优雅。

提高可读性

位置参数和下标很漂亮

但关键字和名称更好

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

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

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

更好的方法

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

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

更好的方法

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

创建一个nametuple

unpack序列

更好的方法

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

更新多个变量的状态

更好的方法

第一种方法的问题

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

操作有顺序要求

太底层太细节

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

同时状态更新

更好的方法

效率

优化的基本原则

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

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

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

连接字符串

更好的方法

更新序列

更好的方法

装饰器和上下文管理

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

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

起个好名字很关键

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

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

更好的方法

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

分离临时上下文

更好的方法

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

如何打开关闭文件

更好的方法

如何使用锁

更好的方法

分离出临时的上下文

更好的方法

ignored是Python 3.4加入的。

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

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

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

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

分离临时上下文

更好的写法

redirect_stdout在Python 3.4加入(译注:contextlib.redirect_stdout)。

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

简洁的单句表达

两个冲突的原则:

一行不要有太多逻辑

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

Raymond的原则:

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

列表解析和生成器

更好的方法

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

(完)

看完本文有收获?请转发分享给更多人

关注「Python那些事」,做全栈开发工程师

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181106B0BYVD00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券