Python 编码风格指南

原文:http://python.jobbole.com/84618/html

本文超出 PEP8 的范畴以涵盖我认为优秀的 Python 风格。本文虽然坚持己见,却不偏执。不只仅涉及语法、模块布局等问题,同时深刻范式、组织及架构的领域。但愿本文能成为精简版 Python 代码《风格的要素》python

目次

基本听从 PEP 准则

…… 可是,命名和单行长度更灵活。git

PEP8 涵盖了诸如空格、函数/类/方法之间的换行、import、对已弃用功能的警告之类的寻常东西,大都不错。github

应用这些准则的最佳工具是 flake8,还能够用来发现一些愚蠢的语法错误。web

PEP8 本来只是一组指导原则,没必要严格甚至虔诚地信奉。必定记得阅读 PEP8 「愚蠢的一致性就是小人物的小妖精」一节。若要进一步了解,能够听一下 Raymond Hettinger 的精彩演讲,「超越 PEP8」正则表达式

惟一引发过多争议的准则事关单行长度和命名。要调整起来也不难。数据库

灵活的单行长度

如果厌烦 flake8 死板的单行长度不得超过 79 个字符的限制,彻底能够忽略或修改这一准则。这仍然不失为一条不错的经验法则,就像英语中句子不能超过 50 个单词,段落不能超过 10 个句子之类的规则同样。这是 flake8 配置文件 的连接,能够看到 max-line-length配置选项。值得注意的是,能够给要忽略 flake8 检查的那一行加上 # noqa 注释,可是请勿滥用。编程

尽管如此,超过九成的代码行都不该该超过 79 个字符,缘由很简单,「扁平胜于嵌套」。若是函数每一行都超出了 79 个字符,确定有别的东西出错了,这时要看看代码而不是 flake8 配置。json

一致的命名

关于命名,遵循几条简单的准则就能够避免众多足以影响整个小组的麻烦。vim

推荐的命名规则

下面这些准则大多改编自 Pacoo 小组

  • 类名:驼峰式 和首字母缩略词:HTTPWriter 优于 HttpWriter
  • 变量名:lower_with_underscores
  • 方法名和函数名:lower_with_underscores
  • 模块名:lower_with_underscores.py。(可是不带下划线的名字更好!)
  • 常量名:UPPER_WITH_UNDERSCORES
  • 预编译的正则表达式:name_re

一般都应该遵循这些准则,除非要参照其余工具的命名规范,好比数据库 schema 或者消息格式。

还能够用 驼峰式 给相似类却不是类的东西命名。使用 驼峰式 的主要好处在于让人们以「全局名词」来关注某个东西,而不是看做局部标记或动词。值得注意的是,Python 给 TrueFalse 和 None 这些根本不是类的东西命名也是用 驼峰式

不要用前缀后缀

…… 好比 _prefix 或 suffix_ 。函数和方法名能够用 _prefix 标记来暗示其是「私有的」,可是最好只在编写预期会普遍使用的 API 以及用 _prefix 标记来隐藏信息的时候谨慎使用。

PEP8 建议使用结尾的下划线来避免与内置关键字重名,好比:

临时这样用也能够,不过最好仍是选一个别的名字。

用 __mangled 这种双下划线前缀给类/实例/方法命名的状况很是少,这实际上涉及特殊的名字修饰,很是罕见。不要起 __dunder__ 这种格式的名字,除非要实现 Python 标准协议,好比 __len__;这是为 Python 内部协议保留的命名空间,不该该在其中增长自定义的东西。

不要用单字符名字

(不过)一些常见的单字符名字能够接受。

在 lambda 表达式中,单参数函数能够命名为 x 。好比:

解包元组时能够用 _ 丢弃不须要的标记。好比:

意思就是说「忽略第一个元素」。

和 lambda 相似,在解析列表/字典/集合的时候,以及在生成器表达式或者一到两行的 for 循环中,可使用单字符迭代标记。一般选择 x,好比:

能够求 items 序列中全部正整数之和。

此外比较常见的是 i,表明 index,一般和内置的 枚举 一块儿使用。好比:

除却上述情形,要极少甚至避免使用单字符用做标记/参数/方法的名字。由于这样就没法用grep 进行检索了。

使用 self 及相似的惯例

应该:

  • 永远将方法的第一个变量命名为 self
  • 永远将 @classmethod 的第一个参数命名为 cls
  • 永远在变量参数列表中使用 *args 和 **kwargs

不要在这些地方吹毛求疵

不遵循以下准则没有什么好处,干脆照它说的作。

永远继承自 object 并使用新式类

 

对于 Python 2 来讲遵循这条准则很重要。不过因为 Python 3 全部的类都隐式继承自 object,这条准则就没有必要了。

不要在类中重复使用实例标记

 

 

列表/字典/集合推导式优于 map/filter

 

尽管在大多数简单状况下最好使用解析表达式,不过有时候 map() 或者 filter() 可读性更佳,须要本身判断。

用圆括号 (...) 折行

 

 

用圆括号 (...) 写 API 更利落

 

 

函数调用中使用隐式行延续

 

 

用 isinstance(obj, cls), 不要用 type(obj) == cls

由于 isinstance 涵盖更多情形,包括子类和抽象基类。同时,不要过多使用 isinstance,由于一般应该使用鸭子类型!

用 with 处理文件和锁

with 语句可以巧妙地关闭文件并释放锁,哪怕是在触发异常的状况下。因此:

 

和 None 相比较要用 is

None 值是一个单例,可是检查 None 的时候,实际上不多真的要在 左值上调用 __eq__。因此:

好的写法不只执行更快,并且更准确。使用 == 并不会更简洁,因此请记住本规则!

不要修改 sys.path

经过 sys.path.insert(0, "../") 等操做来控制 Python 的导入方法或许让人心动,可是要坚定避免这样作。

Python 有一套有几分复杂,却易于理解的模块路径解决方法。能够经过 PYTHONPATH 或诸如 setup.py develop 的技巧来调整 Python 导入模块的方法。还能够用 -m 运行 Python 获得须要的效果,好比使用 python -m mypkg.mymodule 而不是 python mypkg/mymodule.py。代码可否正常运行不该依赖于当前执行 Python 的工做路径。David Beazley 用 PDF 幻灯片再一次扭转了你们的见解,值得一读,“Modules and Packages: Live and Let Die!”

尽可能不要自定义异常类型

…… 若是必定要,也不要建立太多。

要知道 Python 引入了 丰富的内建异常类。值得充分利用。并且经过描述那些触发特定错误条件的字符串消息,来实例化这些异常类,就能达到「定制」的目的。在用户代码中抛出 ValueError(参数错误),LookupError(键错误)以及 AssertionError(用 assert 语句)最为常见。

至因而否应该本身建立异常类有一个不错的经验法则,也就是搞清楚函数调用方每次调用函数之时是否都应该捕获该异常。若是是,那么的确应该本身建立异常类。不过这至关少见。关于这类明显不得不使用的自定义异常类有一个不错的例子,tornado.web.HTTPError。可是要留心 Tornado 是如何避免走极端的:框架或用户代码抛出的全部 HTTP 错误同属一个异常类。

短文档字符串应是名副其实的单行句子

 

把三引号 """ 放在同一行,首字母大写,以句号结尾。四行精简到两行,__doc__ 属性没有糟糕的换行,最吹毛求疵的人也会满意的!

文档字符串使用 reST

标准库和大多数开源项目皆是如此。Sphinx 提供支持,开箱即用。赶忙试试吧!Python requests 模块由此取得了极佳的效果。看看requests.api 模块的例子。

删除结尾空格

最挑剔也不过如此了吧,但是若作不到这一点,有些人可能会被逼疯。不乏能自动搞定这一切的编辑器;这是我用 vim 的实现

文档字符串要写好

下面是在函数文档字符串中使用 Sphinx 风格的 reST 的快速参考:

不要为文档而写文档。写文档字符串要这样思考:

也就是说,上例中没有必要说 timeout 是 float,默认值 5.0,显然是 float。在文档中指出其语义是「秒」更有用,就是说 5.0 意思是 5 秒钟。同时调用方不知道 qsargs 应该是什么,因此用 type 注释给出提示,调用方也无从知道函数的预期返回值是什么,因此 rtype注释是合适的。

最后一点。吉多·范罗苏姆曾说过,他对 Python 的主要领悟是「读代码比写代码频率更高」。直接结论就是有些文档有用,更多的文档有害

基本上只须要给预计会频繁重用的函数写文档。若是给内部模块的每个函数都写上文档,最后只能获得更加难以维护的模块,由于重构代码之时文档也要重构。不要「船货崇拜」文档字符串,更不要用工具自动生成文档。

范式和模式

是函数仍是类

一般应该用函数而不是类。函数和模块是 Python 代码重用的基本单元,仍是最灵活的形式。类是一些 Python 功能的「升级路径」,好比实现容器,代理,描述符,类型系统等等。可是一般函数都是更好的选择。

或许有人喜欢为了更好地组织代码而将关联的函数归在类中。但这是错的。关联的函数应该归在模块中。

尽管有时能够把类看成「小型命名空间」(好比用 @staticmethod)比较有用,一组方法更应该对同一个对象的内部操做有所贡献,而不只仅做为行为分组。

与其建立 TimeHelper 类,带有一堆不得不引入子类才能使用的方法,永远不如直接为时间相关的函数建立 lib.time 模块。类会增殖出更多的类,会增长复杂性,下降可读性。

生成器和迭代器

生成器和迭代器是 Python 中最强大的特性 —— 应该掌握迭代器协议,yield 关键字和生成器表达式。

生成器不只仅对要在大型数据流上反复调用的函数十分重要,并且可让自定义迭代器更加简单,从而简化了代码。将代码重构为生成器一般能够在使得代码在更多场景下复用,从而简化代码。

Fluent Python 的做者 Lucinao Ramalho 经过 30 分钟的演讲,「迭代器和生成器: Python 之道」,给出了一个出色的,快节奏的概述。Python Essential Reference 和 Python Cookbook 的做者 David Beazley 有个深奥的三小时视频教程,「生成器:最后的前沿」,给出了使人知足的生成器用例的详细阐述。由于应用普遍,掌握这一主题很是有必要。

声明式仍是命令式

声明式编程优于命令式编程。代码应该代表你想要作什么,而不是描述如何去作。Python 的函数式编程概览介绍了一些不错的细节并给出了高效使用该风格的例子。

使用轻量级的数据结构更好,好比 列表字典元组集合。将数据展开,编写代码对其进行转换,永远要优于重复调用转换函数/方法来构建数据。

一个例子是重构常见的列表解析:

应该改为 :

可是还有一个不错的例子是将 if/elif/else 链改为 字典 查询。

「纯」函数和迭代器更好

这是个从函数式编程社区借来的概念。这种函数和迭代器亦被描述为「无反作用」,「引用透明」或者有「不可变输入/输出」。

一个简单的例子,要避免这种代码:

这个函数应该从新写成:

这是个惊人的例子。函数不只更加纯粹,并且更加精简了。不只更加精简,并且更好。这里的纯粹是说 assert dedupe(items) == dedupe(items) 在「好」版本中恒为真。在「坏」版本中, num_dupes 在第二次调用时为 0,这会在使用时致使难以理解的错误。

这个例子也阐明了命令式风格和声明式风格的区别:改写后的函数读起来更像是对须要的东西的描述,而不是构建须要的东西的一系列操做。

简单的参数和返回值类型更好

函数应该尽量处理数据,而不是自定义的对象。简单的参数类型更好,好比字典集合元组列表intfloat 和 bool。从这些扩展到标准库类型,好比 datetimetimedeltaarrayDecimal 以及 Future。只有在真的必要时才使用自定义类型。

判断函数是否足够精简有个不错的经验法则,问本身参数和返回值是否老是能够 JSON 序列化。结果证实这个经验法则至关有用:能够 JSON 序列化一般是函数在并行计算时可用的先决条件。可是,就本文档而言,主要的好处在于:可读性,可测试性以及整体的函数简单性。

避免「传统的」面向对象编程

在「传统的面向对象编程语言」中,好比 Java 和 C++ ,代码重用是经过类的继承和多态或者语言声称的相似机制实现的。对 Python 而言,尽管可使用子类和基于类的多态,事实上在地道的 Python 程序中这些功能极少使用。

经过模块和函数实现代码重用更为广泛,经过鸭子类型实现动态调度更为常见。若是发现本身经过超类实现代码重用,停下来,从新思考。若是发现本身大量使用多态,考虑一下是否用 Python 的 dunder 协议或者鸭子类型策略会更好。

看一下另外一个不错的 Python 演讲,一位 Python 核心贡献者的 「不要再写类了」。演讲者建议,若是构建的类只有一个命名像一个类的方法(好比 Runnable.run()),那么实际上只是用函数模拟了一个类,这时候应该停下来。由于在 Python 中,函数是「最高级的」类型,没有理由这样作。

Mixin 有时也没问题

可使用 Mixin 实现基于类的代码重用,同时不须要走极端使用类型层次。可是不要滥用。「扁平胜于嵌套」也适用于类型层次,因此应该避免仅仅为了分解行为而引入没必要要的必须层次的一层。

Mixin 实际上不是 Python 的特性,多亏了 Python 支持多重继承。能够建立基类将功能「注入」到子类中,而没必要构成类型层次的「重要」组成部分,只须要将基类列入 bases列表中的第一个元素。

要考虑顺序,同时不妨记住:bases 自底向上构成层次结构。这里可读性的好处在于关于这个类所须要知道的一切都包含在定义自己:「它混入了权限行为,是专门定制的 Tornado RequestHandler。」

当心框架

Python 有大量的 web,数据库等框架。Python 语言的一大乐趣在于建立自定义框架至关简单。使用开源框架时,应该注意不要将本身的「核心代码」和框架自己结合得过于紧密。

考虑为本身的代码建立框架的时候应当慎之又慎。标准库有不少内置的东西,PyPI 有的就更多了,并且一般你不会须要它

尊重元编程

Python 经过一些特性来支持 「元编程」,包括修饰器,上下文管理器,描述符,import 钩子,元类和抽象语法树(AST)转换。

应该可以自如地使用并理解这些特性,做为 Python 的核心组成部分这些特性有着充分地支持。可是应当意识到使用这些特性之时,也引入了复杂的失败场景。因此,要把为本身的代码建立元编程工具与决定「建立自定义框架」同等对待。它们意味着同一件事情。真要这么作的时候,把元编程工具写成独立的模块,写好文档!

不要惧怕 「双下划线」方法

许多人将 Python 的元编程工具和其对 「双下划线」或「dunder」方法(好比 __getattr__)的支持混为一谈。

正如博文 ——「Python 双下划线,双倍惊喜」—— 所言,双下划线没有什么「特殊的」。它们只不过是 Python 核心开发人员为全部的 Python 内部协议所起的轻量级命名空间。毕竟,__init__ 也是双下划线,没有什么神奇的。

的确,有些双下划线比其余的会致使更多使人困惑的结果,好比,没有很好的理由就重载操做符一般不是什么好主意。可是它们中也有许多,好比 __repr____str____len__ 以及 __call__ 是 Python 语言的完整组成部分,应该在地道的 Python 代码中充分利用。不要回避!

代码风格小禅理

做为一位核心 Python 开发者,Barry Warsaw 曾经说过「Python 之禅」(PEP 20)被用做 Python 代码风格指南使他沮丧,由于这本是为 Python 的内部设计所做的一首小诗。也就是语言的设计以及语言的实现自己。不过必须认可,PEP 20 中有些行能够看成至关不错的地道 Python 代码指南,因此咱们就把它加上了。

美胜于丑

这一条有些主观,实际上等同于问:接手代码的人会被折服仍是感到失望?若是接手的人就是三年后的你呢?

显胜于隐

有时为了重构以去除重复的代码,会有一点抽象。应该可以将代码翻译成显现的英语而且大体了解它是干什么的。不该该有太多的「神奇之处」。

扁平胜于嵌套

这一条很好理解。最好的函数没有嵌套,既不用循环也不用 if 语句。第二好的函数只有一层嵌套。若是有两层及以上的嵌套,最好重构成更小的函数。

一样,不要惧怕将嵌套的 if 语句重构为多部分布尔条件。好比:

最好写成:

 

可读性确实重要

不要惧怕用 # 添加行注释。也不要滥用或者写过多文档。一点点逐行解释,一般颇有帮助。不要惧怕使用稍微长一些的名字,由于描述性更好。将 「response」写成「rsp」没有任何好处。使用 doctest 风格的例子在文档字符串中详细说明边界状况。简洁至上!

错误不该被放过

单独的except: pass 子句危害最大。永远不要使用。制止全部的异常实在危险。将异常处理限制在一行代码,而且老是将 except 处理器限制在特定的类型下。除此以外,能够自如地使用 logging 模块和 log.exception(…)

若是实现难以解释,那就是个坏主意

这虽是通用软件工程原则,可是特别适用于 Python 代码。大多数 Python 函数和对象均可以有易于解释的实现。若是难以解释,极可能是一个坏主意。一般能够经过「分而治之」将一个难以解释的函数重写成易于解释的函数,也就是分割成多个函数。

测试是个好主意

好吧,咱们篡改了「Python 之禅」中的这一行,原文中「命名空间」才是个绝妙的好主意。

不过说正经的,优雅却没有测试的代码简直比哪怕是最丑陋却测试过的代码还要差劲。至少丑陋的代码能够重构成优雅的,可是优雅的代码却不能重构为能够证实是正确的代码,至少不写测试是作不到的!因此,写测试吧!拜托!

势均力敌

咱们把宁愿不去解决的争论放在这个部分。不要由于这些重写别人的代码。这里的东西能够自由地交替使用。

str.format 仍是重载格式化操做符 %

str.format 更健壮,然而 % 使用 "%s %s" printf 风格的字符串更加简洁。二者会永远共存。

若是须要保存 unicode,记得在格式模板中使用 unicode 字符串:

若是最后选择 %,应该考虑 "%(name)s" 语法,从而可使用字典而不是元组,好比:

此外,不要从新发明轮子。str.format 有一点毫无疑问比 % 更好,那就是支持各类格式化模式,好比人性化的数字和百分数。直接用。

可是选择哪个都没有问题。咱们没有强制规定。

if item 仍是 if item is not None

本条和以前的对于 None 是用 == 仍是 is 没有关系。这里咱们实际上利用了 Python 的 「真实性规则」来处理 if item,这其实是「item 不是 None 或者空字符串」的简写。

Python 中的真实性有些复杂。显然第二种写法对于某些错误而言更安全。可是第一种写法在 Python 代码中很是常见,并且更短。对此咱们并无强制规定。

隐式多行字符串仍是三引号 """

Python 编译器在语法分析时,若是多个字符串之间没有东西,会自动将其拼接为一个字符串。好比:

大体上等价于:

第一种写法能够保持缩进整洁,可是须要丑陋的换行符,第二种写法不须要换行符,可是打破了缩进。咱们没有强制规定哪一种更好。

在类仍是实例上使用 raise

事实上给 raise 语句传入异常或者异常实例均可以。好比,下面两行代码大体等价:

本质上,Python 会将第一种写法自动转换为第二种。或许第二个写法更好,即便没有其余缘由,它也能实际上提供一个有用的理由,就像有条有用的消息来解释为何 ValueError会发生同样。可是这两种写法等价的,不该该为这一点就重写代码。咱们不作强制规定。

标准工具和项目结构

咱们选择了一些「最佳组合」工具,以及像样的 Python 项目会用到的最小初始结构。

标准库

  • import datetime as dt: 永远像这样导入 datetime
  • dt.datetime.utcnow(): 优于 .now(), 后者使用的是当地时间
  • import json: 数据交换的标准
  • from collections import namedtuple: 用来作轻量级数据类型
  • from collections import defaultdict: 用来计数/分组
  • from collections import deque: 快速的双向队列
  • from itertools import groupby, chain: 为了声明式风格
  • from functools import wraps: 用来编写合乎规范的装饰器
  • argparse: 为了构建「健壮的」命令行工具
  • fileinput: 用来快速构建易于和 UNIX 管道结合使用的工具
  • log = logging.getLogger(__name__): 足够好用的日志
  • from __future__ import absolute_import: 修复导入别名

常见第三方库

  • python-dateutil 用来解析时间和日历
  • pytz 用来处理时区
  • tldextract 为了更好地处理 URL
  • msgpack-python 比 JSON 更加紧凑地编码
  • futures 为了 Future/pool 并发原语
  • docopt 用来快速编写一次性命令行工具
  • py.test 用来作单元测试,与 mock 和 hypothesis 结合使用

本地开发项目框架

对全部的 Python 包和库而言:

  • 根目录下不要有 __init__.py:目录名用做包名!
  • mypackage/__init__.py 优于 src/mypackage/__init__.py
  • mypackage/lib/__init__.py 优于 lib/__init__.py
  • mypackage/settings.py 优于 settings.py
  • README.rst 用来给新手描述本项目;使用 rst
  • setup.py 用来构建简单工具,好比 setup.py develop
  • requirements.txt 是为 pip 准备的包依赖环境
  • dev-requirements.txt 是为 tests/local 准备的额外的依赖环境
  • Makefile 用来简化 (!!!) build/lint/test/run 步骤

另外,永远记得详细说明包依赖环境

灵感来源

下面这些连接或许能够给你启发,有助于书写具备良好风格和品味的 Python 代码。

出发吧,写更具 Python 风格的代码!

 

撰稿人

  • Andrew Montalenti (@amontalenti): 原做者
  • Vincent Driessen (@nvie): 编辑并提出意见
相关文章
相关标签/搜索