点赞再看,养成习惯!觉得不过瘾的童鞋,欢迎关注公众号《机器学习算法工程师》,有非常多大神的干货文章可供学习噢…
目录
前言
最近小编在学习requests源码时遇到了不少pythonic代码,十分精妙,实在欢喜得很,于是便产生了想系统学习下py代码规范的念头,这便是这篇文章的由来。闲话少叙,上干货吧^ - ^
正文
介绍
本文档给出了构成主要Python发行版中标准库的Python代码的编码约定。关于Python[1]的C实现中的C代码,请参阅相应的信息性PEP描述风格指南。
本文和 PEP 257(Docstring Conventions)改编自Guido的原始Python风格指南文章,并从Barry的风格指南[2]中添加了一些内容。
这个风格指南会随着时间的推移而发展,会出现新的约定并且过去的约定会因为语言本身的变化而过时。
许多项目都有自己的编码风格指南。在任何冲突的情况下,这种特定于项目的指南对于该项目而言应该优先考虑。
不要盲目保持一致性(the Hobgoblin of Little Minds)
Guido的一个关键见解是,阅读代码的次数比编写代码的次数要多。这里提供的指导方针旨在提高代码的可读性,并使其在各种Python代码中保持一致。正如PEP 20所说,“可读性很重要”。
风格指南是关于一致性的。与这个风格指南保持一致非常重要。项目内部的一致性更为重要。在一个模块或函数的一致性是最重要的。
但是,要知道什么时候不一致——有时候风格指南的建议并不适用。当你有疑问的时候,相信自己。看看其他的例子并决定采取哪种方案。不要害怕问问题!
特别是:不要强行为了与这个PEP保持一致而破坏向后兼容性(backwards compatibility)!
能够忽视指定的指南的一些其他理由有:
- 当应用这个指导原则时,代码的可读性会降低,即使是习惯阅读遵循这个PEP的代码的程序猿也是如此看法。
- 与周围不遵循约定的代码保持一致(可能是由于历史原因)——尽管这也是一个清理重构其他人脏乱差代码的机会(以真正的XP风格)。
- 因为所讨论的代码在指南引入之前就已经存在了,并且没有其他理由需要修改该代码。
- 当代码需要与不遵循这份风格指南推荐特性的旧版本的Python保持兼容。
代码布局
缩进
使用4个空格缩进。
连续行应该使用Python的隐式行连接括号、方括号和大括号,或者使用悬挂缩进[7]对包装的元素进行垂直对齐。在使用悬挂缩进时,应考虑以下因素;在第一行应该没有参数,进一步的缩进应该被用来清楚地区分自己是一个延续行:
# Correct:
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# Hanging indents should add a level.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
# Wrong:
# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Further indentation required as indentation is not distinguishable.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
对于延续行,4空格规则是可选的。
可选:
# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
当一个if语句(if-statement)的条件部分太长了以致于需要跨多行写时,值得注意的是,if加上一个空格还有(,创建一个后续多行都是4空格缩进的条件。这可能会与嵌套在if语句中的代码集产生视觉冲突,因为该代码集也是缩进4个空格。对于这种情况如何区分,PEP不采取明确的立场。在这种情况下可以接受的选项包括但不限于:
# No extra indentation.
if (this_is_one_thing and
that_is_another_thing):
do_something()
# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()
# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
and that_is_another_thing):
do_something()
(也可参考下面关于是否在二元操作符binary operators之前或之后断开的讨论。)
在多行结构中,匹配的右括号可以在列表最后一行的第一个非空白字符下面,如:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
或者它可以排在多行结构开始行的第一个字符下面,如:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
制表符或空格(Tabs or Spaces)?
空格是推荐的缩进方式。
使用制表符只是为了与已经用制表符缩进的代码保持一致。
Python 3不允许在缩进中混合使用制表符和空格。
混合使用制表符和空格进行缩进的Python 2代码应该转换为只使用空格。
当使用-t选项调用Python 2命令行解释器时,它会发出针对那些非法混合使用制表符和空格的代码的警告。当使用-tt时,这些警告会变成错误。这些选项是强烈推荐的!
行的最长长度
限制所有行最多为79个字符。
对于输出结构限制较少的长文本块(文档字符串或注释),行长度应限制为72个字符。
限制所需的编辑器窗口宽度可以让几个文件并肩打开,并且当使用以相邻列显示两个版本的代码审查工具时能工作得很好。
大多数工具中的默认wrap破坏了代码的可视化结构,使其更加难以理解。选择这些限制是为了避免在窗口宽度设置为80的编辑器中换行,即使在换行时工具在最后一列中放置了标记符号。一些基于web的工具可能不提供动态wrap。
有些团队更喜欢长行。对于这些团队维护的代码,可以将行长度限制增加到99个字符,前提是注释和文档字符串仍然wrap为72个字符。
Python标准库比较保守,要求每行限制在79个字符(文档字符串/注释限制在72个字符)。
wrap长行推荐的方法是在括号内使用Python的隐式行延续。通过将表达式wrap在括号中,可以将长行拆分为多行。相比使用反斜杠来做到行延续,这个方法更值得采用。
有时反斜杠仍然是合适的。例如,长并且多个with语句不能使用隐式延续,所以反斜杠是可以接受的:
with open('/path/to/some/file/you/want/to/read') as file_1, \
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())
(关于多行if语句的缩进,请参考前面关于多行with语句的讨论。)
另一种情况是assert语句。
确保适当地缩进延续行。
换行符应该在二元运算符之前还是之后?
几十年来,推荐的风格是在二元操作符之后断开。但是这可能在两个方面降低可读性:操作符往往分散在屏幕上的不同列,每个操作符都从它的操作数移到前一行。这样一来给眼睛增加了负担,来区分哪些项目是增加的,哪些是减少的:
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
为了解决这个可读性问题,数学家和他们的出版商遵循相反的惯例。Donald Knuth在他的计算机和排版系列中解释了这个传统规则:“虽然一个段落中的公式总是在二元操作和关系之后断开,但显示的公式总是在二元操作之前断开”[3]。
遵循数学惯例通常导致更易读的代码:
# Correct:
# easy to match operators with operands
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
在Python代码中,允许在二元操作符之前或之后断开,只要局部保持一致即可。对于新代码,建议使用Knuth的风格。
空行
最外层函数和类定义之间用两个空行分开。
类内的方法之间用一个空行分开。
可以(有节制地)使用额外的空行来分隔一组相关函数。在一组相关行(例如一组dummy implementations)之间可以省略空行。
在函数中适当地使用空行来分开不同的逻辑内容。
Python接受control-L(即^L)形式的feed字符作为空格;许多工具将这些字符作为页面分隔符,因此可以使用它们来分隔文件相关部分的页面。请注意,一些编辑器和基于web的代码查看器可能不会将control-L识别为form feed,并将在其位置显示另一个符号。
源文件编码
核心Python发行版中的代码应该始终使用UTF-8(或Python 2中的ASCII)。
使用ASCII(在python2中)或UTF-8(在python3中)的文件不应该有编码声明。
在标准库中,非默认编码只能用于测试目的,或者当注释或文档字符串需要提到包含非ASCII字符的作者名字时;否则,使用\x、\u、\u或\N转义是在字符串文本中包含非ascii数据的首选方法。
对于Python 3.0及以后版本,标准库制定了以下策略(请参考PEP 3131): Python标准库中的所有标识符必须使用ASCII的标识符,并且应该在可行的情况下使用英语单词(在许多情况下,使用的是非英语的缩写和技术术语)。此外,字符串常量和注释也必须是ASCII格式的。唯一可以例外的是(a)测试非ASCII特性的测试用例,和(b)作者的名字。名字不是基于拉丁字母(Latin -1, ISO/IEC 8859-1字符集)的作者必须提供其名称在该字符集中的音译。
鼓励面向全球用户的开放源码项目采用类似的策略。
导入(Imports)
导入通常应该单独一行:
# Correct:
import os
import sys
# Wrong:
import sys, os
这样的可以:
# Correct:
from subprocess import Popen, PIPE
导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,模块全局变量和常量之前。
导入应按以下顺序分组:
- 标准库进口。
- 相关第三方导入。
- 本地应用程序/库特定导入。
您应该在每个组导入之间放一个空行。
推荐使用绝对路径导入,因为如果导入系统配置不正确(比如包中的一个目录最后出现在sys.path上),那么绝对路径导入通常更具可读性,并且表现得更好(或者至少给出更好的错误消息):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
然而,显式相对路径导入是绝对路径导入的一个可替代选择,特别是在处理复杂的包布局时,使用绝对路径导入会非常麻烦:
from . import sibling
from .sibling import example
标准库代码应该避免复杂的包布局,并始终使用绝对路径导入。
隐式相对路径导入永远不应该使用,并且已经在Python 3中删除了。
当从包含类的模块中导入类时,通常可以拼写为:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果这种拼写导致本地名称冲突,则显式拼写:
import myclass
import foo.bar.yourclass
使用"myclass.MyClass" 和"foo.bar.yourclass.YourClass"。
应该避免使用通配符导入 (from <module> import *),因为这会使我们不清楚名称空间有哪些库名,从而使读者和许多自动化工具无法理解。有一个站得住脚的通配符导入用例,就是重新发布一个内部接口作为公共API的一部分(例如,使用可选accelerator module的定义重写接口的纯Python实现,以及具体重写哪些定义是事先不知道的)。
当以这种方式重新发布名称时,下面有关公共接口和内部接口的指导原则仍然适用。
模块级Dunder名称
模块级别的“dunders”(即在前面和后面都带有两个下划线的命名),如__all__, __author__, __version__等,应该放置在模块的文档字符串后面但要在任何导入语句(除了__future__导入)前面。Python规定future导入位置必须出现在模块中除文档字符串之外的任何其他代码之前:
"""This is the example module.
This module does stuff.
"""
from __future__ import barry_as_FLUFL
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
import os
import sys
字符串引号(Quotes)
在Python中,单引号字符串和双引号字符串是相同的。本PEP对此不作推荐。选择一条规则并坚持它。但是,当字符串中包含单引号或双引号字符时,使用另一个来避免字符串中的反斜杠,这提高了可读性。
对于三引号字符串,应始终使用双引号字符,以与PEP 257中的Docstring约定一致。
表达式和语句中的空格(Whitespace)
怪癖(Pet Peeves)
在下列情况下避免不必要的空格:
紧贴括号:
# Correct:
spam(ham[1], {eggs: 2})
# Wrong:
spam( ham[ 1 ], { eggs: 2 } )
在后逗号和后括号之间:
# Correct:
foo = (0,)
# Wrong:
bar = (0, )
紧接在逗号、分号或冒号前面的:
# Correct:
if x == 4: print x, y; x, y = y, x
# Wrong:
if x == 4 : print x , y ; x , y = y , x
但是,在切片中,冒号的作用类似于二元运算符,并且应该在每一边都有相等的数量(将其视为优先级最低的运算符)。在扩展切片中,两个冒号应用的间距必须相同。例外:当切片参数被省略时,空间被省略:
# Correct:
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
# Wrong:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
在函数调用参数列表开始的左括号之前:
# Correct:
spam(1)
# Wrong:
spam (1)
紧接开始索引或切片的左括号之前:
# Correct:
dct['key'] = lst[index]
# Wrong:
dct ['key'] = lst [index]
赋值(或其他)操作符周围的多个空格,使其与其他操作符对齐:
# Correct:
x = 1
y = 2
long_variable = 3
# Wrong:
x = 1
y = 2
long_variable = 3
其他建议
避免尾随空格。因为它通常是不可见的,所以它可能会让人感到困惑:例如,反斜杠后面跟着空格和换行符不被视为行继续标记。有些编辑器不保存它并且许多项目(比如CPython本身)都有拒绝这种空格的pre-commit hooks。
始终在这些二元操作符的两边用一个空格包围:赋值(=)、扩展赋值(+=,-=等)、比较(==,<, >, !=, <> , <=, >=, in, not in, is, is not)、布尔值(and, or, not)。
如果使用具有不同优先级的操作符,请考虑在优先级最低的操作符周围添加空格。相信自己的判断;但是,使用空格的地方只用一个空格,并且在二元操作符的两边要有相同数量的空格:
# Correct:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# Wrong:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
函数注释(Function annotations)应使用冒号的常规规则,并且如果存在,则->箭头周围始终有空格。 (关于功能注释的更多信息,下面章节会讲到。):
# Correct:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# Wrong:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
当用于指示关键字参数或用于指示未注释的函数参数的默认值时,请勿在=符号周围使用空格:
# Correct:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# Wrong:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
但是,当将参数注释与默认值组合时,请在=符号周围使用空格:
# Correct:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# Wrong:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
通常不建议使用复合语句(同一行上的多个语句):
# Correct:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
而不是:
# Wrong:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
尽管有时可以将比较小的if / for / while的主体放在同一行上,但是对于多子句的语句则永远不要这样做。也要避免折叠这么长的行!
别这样:
# Wrong:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
也不要这样:
# Wrong:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
list, like, this)
if foo == 'blah': one(); two(); three()
何时使用尾随逗号(Trailing Commas)
尾部的逗号通常是可选的,除了在组成一个元素的元组时它们是强制性的(并且在Python 2中,它们具有print语句的语义)。 为了清楚起见,建议后者将逗号用(技术上多余的)括号括起来:
# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',
如果结尾的逗号多余,则在使用版本控制系统时,当存放值,参数或导入项的列表预计会随着时间扩展时,它们通常会很有用。这种模式是指将每个值单独放在一行上,始终添加尾随逗号,并在下一行上添加右括号。但是,在与结束定界符相同的行上加上逗号是没有意义的(在上述单例元组的情况下除外):
# Correct:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注释(Comments)
与代码矛盾的注释比没有注释更糟糕。当代码更改时,记住一定要第一时间更新注释!
注释应为完整句子。第一个单词应大写,除非它是一个以小写字母开头的标识符(请勿更改标识符的大小写!)。
整体注释通常由一个或多个完整句子组成的段落组成,每个句子以句点结尾。
在多句注释中,除了最后一句,您应该在每个句子结尾使用两个空格。
确保您的注释清晰明了,并且其他母语使用者也很容易理解。
来自非英语国家的Python编码人员:请用英语写您的注释,除非您有120%的把握确保只会有会说您的母语的人阅读该代码。
块注释
块注释通常适用于其后的某些(或全部)代码,并且缩进到与该代码相同的级别。块注释的每一行都以#和一个空格开头(除非注释内的文本是缩进的)。
块注释中的段落由包含单个#的行分隔。
行内注释
谨慎使用行内注释。
行内注释是与语句在同一行上的注释。行内注释应与语句至少分隔两个空格。它们应以#和单个空格开头。
行内注释是不必要的,并且如果该行代码的意思显而易见,那么用了行内注释实际上会分散注意力。不要这样做:
x = x + 1 # Increment x
但是有时候,这很有用:
x = x + 1 # Compensate for border
文档字符串(Documentation Strings)
在PEP 257中,指示了良好文档字符串的规范。
为所有公共模块,函数,类和方法编写文档字符串。对于非公共方法,文档字符串不是必需的,但是您应该具有描述该方法功能的注释。该注释应出现在def行之后。
PEP 257描述了良好的文档字符串约定。请注意,最重要的是多行文档字符串结尾的"""应单独位于一行上:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
对于只有一行的文档字符串,请在同一行用"""结尾。
命名约定
Python库的命名约定有点混乱,因此我们永远都无法做到完全一致——尽管如此,这里还是提供一下当前推荐的命名标准。新的模块和软件包(包括第三方框架)应按照这些标准编写,但是如果现有库具有不同的风格,优先内部一致。
首要原则
对于用户而言,作为API公共部分可见的名称应遵循反映用法而不是实现的约定。
描述: 命名风格
有很多不同的命名风格。能够独立于它们的用途来识别正在使用的命名方式。
通常区分以下命名风格:
- b(单个小写字母)
- B(单个大写字母)
- lowercase(小写)
- lower_case_with_underscores(带下划线的小写)
- UPPERCASE(大写)
- UPPER_CASE_WITH_UNDERSCORES(带下划线的大写)
- CapitalizedWords ,大驼峰命名法[4],所有单词的首字母大写
- mixedCase ,小驼峰命名法,与大驼峰不同之处,在于第一个单词首字母小写,其他一样
- Capitalized_Words_With_Underscores (带下划线的大驼峰,最ugly!)
还有一种使用短的唯一前缀将相关名称组合在一起的样式。这在Python中使用不多,出于完整性的考虑而提及。例如,os.stat()函数返回一个元组,它里面的项一般具有诸如st_mode,st_size,st_mtime等的名称。(这样做是为了强调与POSIX系统调用结构的字段的对应关系,这有助于程序员熟悉该结构。)
X11库将前导X用于其所有公共功能。在Python中,通常认为这种样式是不必要的,因为属性和方法的名字以对象为前缀,函数名字以模块名作为前缀。
此外,还可以识别出以下使用前划线或后划线的特殊形式(通常可以将它们与任何大小写惯例结合使用):
- _single_leading_underscore:弱“内部使用”指标。例如。 from M import *不导入名称以下划线开头的对象。
- single_trailing_underscore_:按惯例用于避免与Python关键字发生冲突,例如:
tkinter.Toplevel(master, class_='ClassName')
- __double_leading_underscore:命名类属性时,发生改名机制(name mangling)(在类FooBar中,__boo变为_FooBar__boo;请参见下文)。
- __double_leading_and_trailing_underscore__:存在于用户命名空间中的magic对象或属性。例如。 __init __,__ import__或__file__。请勿发明此类名称;仅按文档记录使用它们。
规范: 命名约定
避免使用的名称
切勿将字符“ l”(小写字母el),“ O”(大写字母oh)或“ I”(大写字母eye)用作单个字符变量名称。
在某些字体中,这些字符与数字1和零没有区别。当尝试使用“ l”时,请改用“ L”。
ASCII兼容性
标准库中使用的标识符必须与ASCII兼容,就像PEP 3131的policy section中所述的。
包和模块名称
模块应使用简短的全小写名称。如果下划线能够提高可读性,那它可以用在模块命名中。尽管不鼓励使用下划线,但Python包也应使用短的全小写名称。
当用C或C++编写的扩展模块提供对应更高级别的的Python(例如,面向对象的接口)接口时,C / C++模块应具有一个下划线(例如_socket)。
类名
类名通常应使用大驼峰命名约定。
如果接口在文档中有记录或主要用作回调的情况下,可以采用函数的命名约定。
请注意,内置(bulitin)名称有一个单独的约定:大多数内置名称是单个单词(或两个单词一起),而大驼峰约定仅用于exception名称和内置常量(builtin constants)。
Type变量名
在PEP 484中引入的type变量的名称通常应使用短名称的大驼峰命名法:T,AnyStr,Num。建议将后缀_co或_contra分别添加到用于声明covariant或contravariant行为的变量中:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
Exception名称
因为异常应该是类,所以此处使用类命名约定。但是,您应该在异常名称加上后缀“ Error”(如果异常实际上是一个错误)。
全局变量名
(我们希望这些变量只能在一个模块内使用。)这些约定与函数的约定大致相同。
被设计用于通过from M import *使用的模块应使用__all__机制以防止导出全局变量,或使用老早的约定在此类全局变量加下划线前缀(您可能需要这样做以表明这些全局变量是“模块非公开的” ”)。
函数和变量名
函数名称应小写,必要时用下划线分隔单词,以提高可读性。
变量名与函数名遵循相同的约定。
仅在已经是主流风格(例如threading.py)的情况下才允许使用小驼峰命名法,以保持向后兼容性。
函数和方法参数
始终将self作为实例方法(instance methods)的第一个参数。
始终对类方法(class methods)的第一个参数使用cls。
如果函数参数的名称与保留关键字发生冲突,通常最好在末尾附加一个下划线,而不要使用缩写或拼写错误。 因此,class_优于clss。 (也许更好的办法是使用同义词来避免此类冲突。)
方法名称和实例变量
使用函数命名规则:小写,必要时用下划线分隔单词,以提高可读性。
仅对非公共方法和实例变量使用一个前导下划线。
为避免名称与子类发生冲突,请使用两个前导下划线来调用Python的改名机制。
Python用类名来修饰这些名称:如果Foo类具有名为__a的属性,则Foo .__ a无法访问它。 (坚持直接访问__a属性的用户仍然可以通过调用Foo._Foo__a获得访问权限。)通常,应仅使用双引号下划线来避免名称与设计为子类的类中的属性发生冲突。
注意:关于__name的使用存在一些争议(请参见下文)。
常量
常量通常在模块级别定义,并以所有大写字母书写,并用下划线分隔单词。示例包括MAX_OVERFLOW和TOTAL。
针对继承的设计
始终确定类的方法和实例变量(统称为“属性”)应该是公共的还是非公共的。如有疑问,请选择非公开;后面将其公开比将公共属性设为不公开要容易。
公共属性指的是你期望与这个类不想关的用户使用并且承诺避免向后不兼容的更改。非公开属性是指不打算给第三方使用的属性;您不能保证非公共属性不会更改以及不会被删除。
我们在这里不使用术语“私有”,因为在Python中没有任何属性是真正私有的(通常没有不必要的工作量)。
另一类属性是属于“子类API”(在其他语言中通常称为“受保护”)的那些属性。某些类被设计为可继承的,以扩展或修改类行为的各个方面。在设计此类时,请务必明确决定哪些属性是公共属性,哪些是子类API的一部分,哪些属性仅由您的基类使用。
考虑到这一点,以下是Pythonic风格的指南:
- 公共属性不应包含前导下划线。
- 如果您的公共属性名称与保留关键字冲突,请在属性名称后附加一个下划线。这比缩写或拼写错误更可取。(但是,尽管有此规则,对于任何已知是类的变量或参数,尤其是类方法的第一个参数,“ cls”是首选的拼写。)
注意1:有关类方法,请参见上面的参数名称建议。 - 对于简单的公共数据属性,最好仅公开属性名称,而不使用复杂的访问器/更改器方法。请记住,如果您发现简单的数据属性需要增强功能行为,那么Python为将来的增强提供了一条简便的途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法之后。
注意1:属性仅适用于new-style类。
注意2:尽管通常不会出现诸如缓存之类的副作用,但是请尽量使功能行为没有副作用。
注意3:避免将属性用于计算开销大的操作;属性符号使调用者认为访问(相对)便宜。 - 如果你的类打算被继承,并且有不希望子类使用的属性,请考虑使用双前导下划线、无尾随下划线来命名它们。这将调用Python的改名机制,其中将类名加入到属性名称中。这有助于避免属性名称冲突,如果子类无意中包含相同名称的属性。
注意1:请注意,改名机制仅使用简单的类名,因此如果子类同时选用了相同的类名和属性名,则仍会发生名称冲突。
注意2:改名机制会导致某些用例(如调试和__getattr __())不那么方便。但是,改名算法已被很好地记录,并且易于手动执行。
注意3:并非每个人都喜欢改名机制。尝试在避免意外名称冲突的需要和上层调用者潜在的使用场景之间找到平衡。
公共和内部接口
任何向后兼容性保证都仅适用于公共接口。因此,重要的是用户能够清楚地区分公共接口和内部接口。
除非文档明确声明它们是临时接口或内部接口不受通常的向后兼容性保证,否则已说明文件的接口被视为公共接口。所有未记录的接口都应假定为内部接口。
为了更好地支持自我检查,模块应该使用__all__属性在其公共API中显式声明名称。将__all__设置为空列表表示该模块没有公共API。
即使正确设置了__all__,内部接口(包,模块,类,函数,属性或其他名称)仍应使用单个下划线作为前缀。
如果包含的命名空间(包,模块或类)是内部的,则该接口也被视为内部接口。
对于导入的名称实现时应仔细考虑。其他模块一定不要直接访问这些导入的名称,除非他们是这些模块API文档明确记录的,例如os.path或从子模块公开函数的包的__init__模块。
编程建议
- 应该以利于Python其他实现(PyPy,Jython,IronPython,Cython,Psyco等)的方式编写代码。
例如,对于形如a += b或a = a + b的语句,请不要依赖CPython有效地实现就地字符串连接。 即使在CPython中,这种优化也是脆弱(fragile)的(仅适用于某些类型),并且在不使用引用计数的实现中根本没有这种优化。 在库的性能敏感部分中,应改用’’.join()形式。 这将确保在各种实现方式中串联发生在线性时间内。 - 与单例(如None)的比较应该始终使用is或is not(而不是equals运算符)进行。
另外,当你想表达的真正意思是if x is not None时,却写成if x的时候就要小心点了,例如当测试是否将默认为None的变量或参数设置为另一个值,这另一个值可能在布尔上下文语境中为false的类型(例如空容器)! - 使用is not 运算符,而不是not … is。虽然两个表达式在功能上都相同,但前者更易读和受推荐:
# Correct:
if foo is not None:
# Wrong:
if not foo is None:
- 当实现具有丰富比较的排序运算时,最好实现全部六个操作符(__eq __,__ ne __,__ lt __,__ le __,__ gt __,__ ge__),而不是依赖于仅进行特定比较的其他代码。
为了最大程度地减少工作量,functools.total_ordering()装饰器提供了一种自动生成缺失的比较方法的工具。
PEP 207指出自反规则(reflexivity rules)由Python假定。 因此,解释器可以将y > x替换为x < y,将y >= x替换为x <= y,并且可以交换x == y和x != y的参数。 保证sort()和min()操作使用< 运算符,而max()数使用> 运算符。 但是,最好实现所有六个操作,以免在其他情况下不会造成混淆。 - 始终使用def语句而不是将lambda表达式直接绑定到标识符的赋值语句:
# Correct:
def f(x): return 2*x
# Wrong:
f = lambda x: 2*x
第一种形式表示结果函数对象的名称专门为“ f”,而不是通用的’<lambda>’。 通常,这对于回溯和字符串表示形式更为有用。 使用赋值语句消除了lambda表达式相对于显式def语句可以提供的唯一好处(即可以将其嵌入较大的表达式中)
-
从Exception而不是BaseException派生异常。从BaseException直接继承的异常通常是捕获它们本身就是错误行为的异常(Direct inheritance from BaseException is reserved for exceptions where catching them is almost always the wrong thing to do.)。
基于捕获异常的代码的区别来设计异常层次结构,而不是根据引发异常的位置。 旨在回答“出了什么问题?”,而不是以编程方式仅声明“发生了问题”(有关内置异常层次结构的学习示例,请参阅PEP 3151)
类命名约定在此处适用,但是如果异常是错误,则应在异常类中添加后缀“ Error”。用于非本地流控制或其他形式的信号的非错误异常不需要特殊的后缀。 -
适当使用异常链(exception chain)。在Python 3中,应使用“raise X from Y”来表示显式替换,而不会丢失原始回溯。
特意替换内部异常时(在Python 2中使用“raise X”或在Python 3.3+中使用“raise X from Y”),请确保将相关详细信息转移到新异常(例如,在将KeyError转换为AttributeError或将原始异常的文本嵌入新的异常消息中时保留属性名称)。 -
在Python 2中引发异常时,请使用raise ValueError(‘message’),而不是较旧的形式raise ValueError, ‘message’。
后一种形式不是合法的Python 3语法。
使用括号的形式还意味着,当异常参数很长或包含字符串格式时,由于包含括号,因此您无需使用行继续符。 -
捕获异常时,请尽可能提及特定的异常,而不要使用光秃秃的except: clause:
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
一个光秃秃(bare)的except: clause 将捕获SystemExit和KeyboardInterrupt异常,这让使用Control-C中断程序更加困难,并且会掩盖其他问题。 如果要捕获所有表示程序错误的异常,请使用except Exception:(bare except等价于except BaseException: )。
一个好的经验法则是在这两种情况下限制使用bare ‘except’ 子句:
-
如果异常处理程序将打印输出或记录回溯;至少用户会意识到发生了错误。
-
如果代码需要做一些清理工作,但是解决这个问题的好办法是用raise. try … finally使异常向上传播。
-
在将捕获的异常绑定到名称时,最好使用Python 2.6中添加的显式名称绑定语法:
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
这是Python 3中唯一支持的语法,并且避免了与较早的基于逗号的语法相关的歧义问题。
- 捕获操作系统错误时,最好使用Python 3.3中引入的显式异常层次结构,而不是对errno值的自我检查。
- 此外,对于所有try/except子句,请将try子句限制为所需的绝对最小数量的代码。同样,这避免了掩盖错误:
# Correct:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
# Wrong:
try:
# Too broad!
return handle_value(collection[key])
except KeyError:
# Will also catch KeyError raised by handle_value()
return key_not_found(key)
- 当资源(如文件等)被位于本地特定代码段使用时,请使用with语句以确保在使用后迅速、可靠地对其进行清理。 try/finally语句也是可以接受的。
- 每当他们执行除获取和释放资源以外的其他操作时,都应通过单独的函数或方法来调用上下文管理器:
# Correct:
with conn.begin_transaction():
do_stuff_in_transaction(conn)
# Wrong:
with conn:
do_stuff_in_transaction(conn)
后面的示例没有提供任何信息来指示__enter__和__exit__方法除了在事务处理后关闭连接外,还在做其他事情。明确作用范围很重要。
- 在返回语句上保持一致。 要么函数中的return语句都应返回一个表达式,要么都不返回。 如果有一个return语句返回一个表达式,则没有值返回的return语句应将其显式声明为return None,并且函数末尾应存在一个显式return语句(如果可达):
# Correct:
def foo(x):
if x >= 0:
return math.sqrt(x)
else:
return None
def bar(x):
if x < 0:
return None
return math.sqrt(x)
# Wrong:
def foo(x):
if x >= 0:
return math.sqrt(x)
def bar(x):
if x < 0:
return
return math.sqrt(x)
- 使用字符串方法而不是字符串模块。
字符串方法总是更快,并且与unicode字符串共享相同的API。如果需要与2.0之前的Python向后兼容,请无视此规则。 - 使用’’.startswith()和’’.endswith()而不是字符串切片来检查前缀或后缀。
startswith()和endswith()更干净并且不容易出错:
# Correct:
if foo.startswith('bar'):
# Wrong:
if foo[:3] == 'bar':
- 对象类型比较应始终使用isinstance()而不是直接比较类型:
# Correct:
if isinstance(obj, int):
# Wrong:
if type(obj) is type(1):
在检查对象是否为字符串时,请记住它也可能是unicode字符串!在Python 2中,str和unicode具有一个公共基类basestring,因此您可以执行以下操作:
if isinstance(obj, basestring):
请注意,在Python 3中,unicode和basestring不再存在(只有str),bytes对象不再是一种字符串(取而代之的是整数序列)。
- 对于序列sequences(字符串strings,列表lists,元组tuples),利用以下事实:空序列为false:
# Correct:
if not seq:
if seq:
# Wrong:
if len(seq):
if not len(seq):
- 不要写有大量尾随空格的字符串文字。这种尾随的空格在视觉上是无法区分的,某些编辑器(或更近期的reindent.py)将对其进行修剪。
- 不要使用==将布尔值与True或False进行比较:
# Correct:
if greeting:
# Wrong:
if greeting == True:
更糟糕的做法:
# Wrong:
if greeting is True:
- 不鼓励在try…finally套件中使用流控制语句return/break/continue,这些流控制语句会跳出finally套件。因为这些语句将隐式取消通过finally套件传播的任何活动异常:
# Wrong:
def foo():
try:
1 / 0
finally:
return 42
函数注解(Annotations)
随着PEP 484的接受,函数注解的风格规则正在改变。
- 为了向前兼容,Python 3代码中的函数注解最好应使用PEP 484语法。(之前的章节中也有一些对注解的格式建议。)
- 不再鼓励使用本PEP以前建议的注解风格进行实验。
- 但是,除了stdlib以外,现在鼓励使用PEP 484规范进行实验。 例如,使用PEP 484风格类型注解来标记大型第三方库或应用程序,查看添加这些注解的难易程度,并观察它们的存在是否提高了代码的可理解性。
- Python标准库在采用注解时应保守一点,但允许将其用于新代码和大型重构。
- 对于想要用函数注解来做点花样的代码,建议添加以下形式的注释:
# type: ignore
放在文件顶部附近;这告诉类型检查器忽视所有注解。 (在PEP 484中可以找到更细粒度的方法来逃过类型检查程序的警告。)
- 与检查器一样,类型检查器是可选的独立工具。默认情况下,Python解释器不应由于类型检查而发出任何消息,也不应基于注解更改其行为。
- 不想使用类型检查器的用户可以随意忽略它们。 但是,预期第三方库包的用户可能希望对那些软件包运行类型检查器。 为此,PEP 484建议使用存根文件(stub files):类型检查器优先于相应的.py文件去读取.pyi文件。 存根文件可以与库一起分发,也可以通过排版过的仓库[5]单独分发(在库作者的许可下)。
- 对于需要向后兼容的代码,可以以注释的形式添加类型注解。请参阅PEP 484 [6]的相关部分。
变量注解
PEP 526引入了变量注解。针对它们的风格建议与上述函数注解类似:
- 模块级变量,类和实例变量以及局部变量的注解应在冒号后面有一个空格。
- 冒号前不应有空格。
- 如果赋值语句有右半边,那么等号两边都应该有一个空格:
# Correct:
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown>'
# Wrong:
code:int # No space after colon
code : int # Space before colon
class Test:
result: int=0 # No spaces around equality sign
- 尽管PEP 526已被Python 3.6接受,但对于所有版本的Python存根文件,变量注解语法是最被推荐的语法(有关详细信息,请参阅PEP 484)。
参考文献
[1] PEP 7, Style Guide for C Code, van Rossum
[2] Barry’s GNU Mailman style guide http://barry.warsaw.us/software/STYLEGUIDE.txt
[3] Donald Knuth’s The TeXBook, pages 195 and 196.
[4] http://www.wikipedia.com/wiki/CamelCase
[5] Typeshed repo https://github.com/python/typeshed### 版权
[6] Suggested syntax for Python 2.7 and straddling code https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
[7] 悬挂缩进(Hanging indentation)是一种类型设置风格,其中段落中除第一行外的所有行均进行缩进。 在Python的上下文中,该术语用于描述一种风格,其中带括号的语句的左括号是该行的最后一个非空白字符,其后的行会缩进,直到右括号为止。
版权
该文档已放置在公共领域。
来自:https://github.com/python/peps/blob/master/pep-0008.txt
结语
在对这篇PEP 8文章翻译过程中,小编结合自己的经验修正了许多翻译软件翻译的语句,但还是有一些地方,是小编浅薄的经验所无法照顾的,就会加上原始英文,后续有新的理解会回来修正。希望大家喜欢这篇译文,对Guido大神推荐的pythonic风格学以致用!
参考文献
- 原文:https://www.python.org/dev/peps/pep-0008/
- Google翻译
- 网易有道翻译