1.PEP8
PEP: 8
Title: Style Guide for Python Code
Author: Guido van Rossum , Barry Warsaw , Nick Coghlan
Status: Active
Type: Process
Created: 05-Jul-2001
Post-History: 05-Jul-2001, 01-Aug-2013
1.1 简介
PEP 8 全称为 Python Enhancement Proposal #8 , 中文叫做《8号Python增强提案》。PEP 8 中列出了很多编程的细节,认真实践有助于养成良好的编程习惯。
Guido的一个重要观点是代码被读的次数远多于被写的次数。这篇指南旨在提高代码的可读性,使浩瀚如烟的Python代码风格能保持一致。正如PEP 20那首《Zen of Python》的小诗里所说的:“可读性很重要(Readability counts)”。
这本风格指南是关于一致性的。同风格指南保持一致性是重要的,但是同项目保持一致性更加重要,同一个模块和一个函数保持一致性则最为重要。
然而最最重要的是:要知道何时去违反一致性,因为有时风格指南并不适用。当存有疑虑时,请自行做出最佳判断。请参考别的例子去做出最好的决定。并且不要犹豫,尽管提问。
特别的:千万不要为了遵守这篇PEP而破坏向后兼容性!
如果有以下理由,则可以忽略这份风格指南:
-
当采用风格指南时会让代码更难读,甚至对于习惯阅读遵循这篇PEP的代码的人来说也是如此。
-
需要和周围的代码保持一致性,但这些代码违反了指南中的风格(可是时历史原因造成的)——尽管这可能也是一个收拾别人烂摊子的机会(进入真正的极限编程状态)。
-
若是有问题的某段代码早于引入指南的时间,那么没有必要去修改这段代码。
-
代码需要和更旧版本的Python保持兼容,而旧版本的Python不支持风格指南所推荐的特性。
1.2 代码布局
1.2.1 每个缩进级别采用4个空格。
连续行所包装的元素应该要么采用Python隐式续行,即垂直对齐于圆括号、方括号和花括号,要么采用悬挂缩进(hanging indent)4。采用悬挂缩进时需考虑以下两点:第一行不应该包括参数,并且在续行中需要再缩进一级以便清楚表示。
正确的例子:
# 同开始分界符(左括号)对齐
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 续行多缩进一级以同其他代码区别
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# 悬挂缩进需要多缩进一级
foo = long_function_name(
var_one, var_two,
var_three, var_four)
错误的例子:
# 采用垂直对齐时第一行不应该有参数
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 续行并没有被区分开,因此需要再缩进一级
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
1.2.2 tab还是空格?
推荐使用空格来进行缩进。
Tab应该只在现有代码已经使用tab进行缩进的情况下使用,以便和现有代码保持一致。
Python 3不允许tab和空格混合使用。
Python 2的代码若有tab和空格混合使用的情况,应该把tab全部转换为只有空格。
1.2.3 每行最大长度
将所有行都限制在79个字符长度以内。
对于连续大段的文字(比如文档字符串(docstring)或注释),其结构上的限制更少,这些行应该被限制在72个字符长度内。
限制编辑器的窗口宽度能让好几个文件同时打开在屏幕上显示,在使用代码评审(code review)工具时在两个相邻窗口显示两个版本的代码效果很好。
很多工具的默认自动换行会破坏代码的结构,使代码更难以理解。在窗口大小设置为80个字符的编辑器中,即使在换行时编辑器可能会在最后一列放置一个记号,为避免自动换行也需要限制每行字符长度。一些基于web的工具可能根本没有自动换行的功能。
一些团队会强烈希望行长度比79个字符更长。当代码仅仅只由一个团队维护时,可以达成一致让行长度增加到80到100字符(实际上最大行长是99字符),注释和文档字符串仍然是以72字符换行。
一种推荐的换行方式是利用Python圆括号、方括号和花括号中的隐式续行。长行可以通过在括号内换行来分成多行。应该最好加上反斜杠来区别续行。
有时续行只能使用反斜杠才。例如,较长的多个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())
1.2.4 二元运算符之前还是之后换行?
推荐以下形式
# 正确的例子:更容易匹配运算符与操作数
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
1.2.5 空行的选择
使用2个空行来分隔最外层的函数(function)和类(class)定义。
使用1个空行来分隔类中的方法(method)定义。
1.2.6 模块引用
Imports应该分行写,而不是都写在一行,例如:
# 分开写
import os
import sys
# 不要像下面一样写在一行
import sys, os
这样写也是可以的:
from subprocess import Popen, PIPE
Imports应该写在代码文件的开头,位于模块(module)注释和文档字符串(docstring)之后,模块全局变量(globals)和常量(constants)声明之前。
Imports应该按照下面的顺序分组来写:
1.标准库imports
2.相关第三方imports
3.本地应用/库的特定imports
4.不同组的imports之前用空格隔开。
避免使用通配符imports(from <module> import *),因为会造成在当前命名空间出现的命名含义不清晰,给读者和许多自动化工具造成困扰。有一个可以正当使用通配符import的情形,即将一个内部接口重新发布成公共API的一部分(比如,使用备选的加速模块中的定义去覆盖纯Python实现的接口,预先无法知晓具体哪些定义将被覆盖)。
1.2.7 模块级的双下划线命名
模块中的“双下滑线”(变量名以两个下划线开头,两个下划线结尾)变量,比如__all__
,__author__
,__version__
等,应该写在文档字符串(docstring)之后,除了form __future__
引用(imports)的任何其它类型的引用语句之前。Python要求模块中__future__
的导入必须出现在除文档字符串(docstring)之外的任何其他代码之前。
例子:
"""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
1.3 字符串引用
在Python中表示字符串时,不管用单引号还是双引号都是一样的。但是不推荐将这两种方式看作一样并且混用。最好选择一种规则并坚持使用。当字符串中包含单引号时,采用双引号来表示字符串,反之也是一样,这样可以避免使用反斜杠,代码也更易读。
对于三引号表示的字符串,使用双引号字符来表示6,这样可以和PEP 257的文档字符串(docstring)规则保持一致。
1.4表达式和语句中的空格
在下列情形中避免使用过多的空白:
- 方括号,圆括号和花括号之后
#正确的例子:
spam(ham[1], {eggs: 2})
#错误的例子:
spam( ham[ 1 ], { eggs: 2 } )
- 在切片操作时,冒号和二元运算符是一样的,应该在其左右两边保留相同数量的空格(就像对待优先级最低的运算符一样)。在扩展切片操作中,所有冒号的左右两边空格数都应该相等。不过也有例外,当切片操作中的参数被省略时,应该也忽略空格。
#正确的例子:
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]
#错误的例子:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
- 赋值(或其他)运算符周围使用多个空格来和其他语句对齐:
#正确的例子:
x = 1
y = 2
long_variable = 3
#错误的例子:
x = 1
y = 2
long_variable = 3
- 一些建议
避免任何行末的空格。因为它通常是不可见的。
在二元运算符的两边都使用一个空格:赋值运算符(=),增量赋值运算符(+=, -= etc.),比较运算符(==, <, >, !=, <>, <=, >=, in, not in, is, is not),布尔运算符(and, or, not)。
如果使用了优先级不同的运算符,则在优先级较低的操作符周围增加空白。请你自行判断,不过永远不要用超过1个空格,永远保持二元运算符两侧的空白数量一样
#正确的例子:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
#错误的例子:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
1.5注释
1.5.1 注释指南
和代码矛盾的注释还不如没有。当代码有改动时,一定要优先更改注释使其保持最新。
注释应该是完整的多个句子。如果注释是一个短语或一个句子,其首字母应该大写,除非开头是一个以小写字母开头的标识符(永远不要更改标识符的大小写)。
如果注释很短,结束的句号可以被忽略。块注释通常由一段或几段完整的句子组成,每个句子都应该以句号结束。
你应该在句尾的句号后再加上2个空格。
使用英文写作,参考Strunk和White的《The Elements of Style》
来自非英语国家的Python程序员们,请使用英文来写注释,除非你120%确定你的代码永远不会被不懂你所用语言的人阅读到。
1.5.2块注释
块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。块注释的每一行都应该以#和一个空格开头(除非该文本是在注释内缩进对齐的)。
块注释中的段落应该用只含有单个#的一行隔开。
1.5.3 行内注释
尽量少用行内注释。
行内注释是和代码语句写在一行内的注释。行内注释应该至少和代码语句之间有两个空格的间隔,并且以#和一个空格开始。
1.5.4 文档字符串
要知道如何写出好的文档字符串(docstring),请参考PEP 257。
-
所有的公共模块,函数,类和方法都应该有文档字符串。对于非公共方法,文档字符串不是必要的,但你应该留有注释说明该方法的功能,该注释应当出现在
def
的下一行。 -
PEP 257描述了好的文档字符应该遵循的规则。其中最重要的是,多行文档字符串以单行
"""
结尾,不能有其他字符,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
1.6命名约定
1.6.1 首要原则
对于用户可见的公共部分API,其命名应当表达出功能用途而不是其具体的实现细节。
1.6.2 需要避免的命名
不要使用字符’l’(L的小写的字母),’O’(o大写的字母),或者’I’(i的大写的字母)来作为单个字符的变量名。
在一些字体中,这些字符和数字1和0无法区别开来。比如,当想使用’l’时,使用’L’代替。
1.6.3 包和模块命名
模块命名应短小,且为全小写。若下划线能提高可读性,也可以在模块名中使用。Python包命名也应该短小,且为全小写,但不应使用下划线。
1.6.4 类命名
类命名应该使用驼峰体(CapWords)的命名约定。
1.6.5 异常命名
由于异常实际上也是类,因此类命名约定也适用与异常。不同的是,如果异常实际上是抛出错误的话,异常名前应该加上”Error”的前缀。
1.6.6 全局变量命名
对于引用方式设计为from M import *的模块,应该使用all机制来避免import全局变量,或者采用下划线前缀的旧约定来命名全局变量,从而表明这些变量是“模块非公开的”。
1.6.7 函数命名
函数命名应该都是小写,必要时使用下划线来提高可读性。
1.6.8 函数和方法参数
实例方法的第一参数永远都是self。
类方法的第一个参数永远都是cls。
在函数参数名和保留关键字冲突时,相对于使用缩写或拼写简化,使用以下划线结尾的命名一般更好。比如,class_比clss更好。(或许使用同义词避免这样的冲突是更好的方式。)
1.6.9 方法命名和实例变量
使用函数命名的规则:小写单词,必要时使用下划线分开以提高可读性。
仅对于非公开方法和变量命名在开头使用一个下划线。
避免和子类的命名冲突,使用两个下划线开头来触发Python的命名修饰机制。
Python类名的命名修饰规则:如果类Foo有一个属性叫__a,不能使用Foo.__a的方式访问该变量。(有用户可能仍然坚持使用Foo._Foo__a的方法访问。)一般来说,两个下划线开头的命名方法仅用于避免与设计为子类的类中的属性名冲突。
1.6.10 常量
常量通常是在模块级别定义的,使用全部大写并用下划线将单词分开。如:MAX_OVERFLOW和TOTAL 。
1.6.11 继承的设计
记得永远区别类的方法和实例变量(属性)应该是公开的还是非公开的。如果有疑虑的话,请选择非公开的;因为之后将非公开属性变为公开属性要容易些。
公开属性是那些你希望和你定义的类无关的客户来使用的,并且确保不会出现向后不兼容的问题。非公开属性是那些不希望被第三方使用的部分,你可以不用保证非公开属性不会变化或被移除。
我们在这里没有使用“私有(private)”这个词,因为在Python里没有什么属性是真正私有的(这样设计省略了大量不必要的工作)。
另一类属性属于子类API的一部分(在其他语言中经常被称为”protected”)。一些类是为继承设计的,要么扩展要么修改类的部分行为。当设计这样的类时,需要谨慎明确地决定哪些属性是公开的,哪些属于子类API,哪些真的只会被你的基类调用。
1.7 编程建议
-
代码应该以不影响其他Python实现(PyPy,Jython,IronPython,Cython,Psyco等)的方式编写。
例如,不要依赖于 CPython 在字符串拼接时的优化实现,像这种语句形式a += b和a = a + b。即使是 CPython(仅对某些类型起作用) 这种优化也是脆弱的,不是在所有的实现中都不使用引用计数。在库中性能敏感的部分,用''.join形式来代替。这会确保在所有不同的实现中字符串拼接是线性时间的。 -
与单例作比较,像None应该用is或is not,从不使用==操作符。
同样的,当心if x is not None这样的写法,你是不知真的要判断x不是None。例如,测试一个默认值为None的变量或参数是否设置成了其它值,其它值有可能是某种特殊类型(如容器),这种特殊类型在逻辑运算时其值会被当作Flase来看待。 -
捕获异常时,尽可能使用明确的异常,而不是用一个空的except:语句
-
另外,对于所有try / except子句,将try子句限制为必需的绝对最小代码量。同样,这样可以避免屏蔽错误。
-
坚持使用return语句。函数内的return语句都应该返回一个表达式,或者None。如果一个return语句返回一个表达式,另一个没有返回值的应该用return None清晰的说明,并且在一个函数的结尾应该明确使用一个return语句(如果有返回值的话)。
-
不要用==比较True和False。
2.Yapf介绍及使用
2.1Yapf 背景
现在的大多数 Python 代码格式化工具(比如:autopep8 和 pep8ify)是可以移除代码中的 lint 错误。这显然有些局限性。比如:遵循 PEP 8 指导的代码可能就不会被格式化了,但这并不说明代码看起来就舒服。
但 YAPF 独辟蹊径。它脱胎于由 Daniel Jasper 开发的 clang-format。大体上来说,这个算法获取代码,然后把初始代码重新编排,即便初始代码并没有违背规范,也可使其达到遵循代码规范的最佳格式。这个理念和 Go 语言中的 gofmt 工具相似,终结关于格式的各种“圣战”。如果一个项目的代码库,无论何时修改,通过 YAPF 优化后,代码风格可统一,在每次 code review 中,也就没有必要争论风格了。
YAPF 的终极目标是生成和遵循代码规范的程序员写出的一样的代码。可帮你减少维护代码的苦差事。
YAPF 支持 Python 2.7 和 3.4.6+
2.2Yapf 安装
pip install yapf
Yapf 使用
# python3 -m yapf -h
usage: __main__.py [-h] [-v] [-d | -i | -q] [-r | -l START-END] [-e PATTERN]
[--style STYLE] [--style-help] [--no-local-style] [-p]
[-vv]
[files [files ...]]
Formatter for Python code.
positional arguments:
files reads from stdin when no files are specified.
optional arguments:
-h, --help show this help message and exit
-v, --version show version number and exit
-d, --diff print the diff for the fixed source # 输出前后不相同的值
-i, --in-place make changes to files in place # 直接修改
-q, --quiet output nothing and set return value
-r, --recursive run recursively over directories # 递归文件下的所有文件
-l START-END, --lines START-END # 指定行号
range of lines to reformat, one-based
-e PATTERN, --exclude PATTERN
patterns for files to exclude from formatting
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg file located in the same
directory as the source or one of its parent
directories (for stdin, the current directory is
used). # 指定格式化的类型,默认为pep8, 还有google,facebook,yapf可选
--style-help show style settings and exit; this output can be saved
to .style.yapf to make your settings permanent
--no-local-style don't search for local style definition
-p, --parallel run yapf in parallel when formatting multiple files. # 并行格式化多个文件
Requires concurrent.futures in Python 2.X
-vv, --verbose print out file names while processing
2.3日常使用
# python3 -m yapf -rip ./
// 配置谷歌代码风格,可选有pep8, google, yapf, facebook
# python3 -m yapf -rip --style=google ./
2.4如何对某一些代码不使用yapf
# yapf: disable
2.5自定义配置代码风格
Yapf 使用的格式化样式是可配置的,并且有许多“旋钮”可用于调优 YAPF 的格式化方式。
2.6忽略某一些文件
在项目根目录建立一个文件.yapfignore
用法与.gitignore
相似
2.7 --style
使用
优先级有高到低
- 命令行上指定
--style='{based_on_style: pep8, indent_width: 2}'
-
.style.yapf
文件中自定义(当前目录)
[style]
based_on_style = pep8
spaces_before_comment = 4
-
setup.config
文件中自定义(当前目录)
[yapf]
based_on_style = pep8
spaces_before_comment = 4
-
~/.config/yapf/style
文件中自定义
[style]
based_on_style = pep8
spaces_before_comment = 4
2.7使用包进行format
主要API有两个:FormatCode
, FormatFile
>>> from yapf.yapflib.yapf_api import FormatCode # reformat a string of code
>>> FormatCode("f ( a = 1, b = 2 )")
'f(a=1, b=2)\n'
>>> FormatCode("def g():\n return True", style_config='pep8')
'def g():\n return True\n'
>>> from yapf.yapflib.yapf_api import FormatFile # reformat a file
>>> print(open("foo.py").read()) # contents of file
a==b
>>> FormatFile("foo.py")
('a == b\n', 'utf-8')
>>> FormatFile("foo.py", in_place=True)
(None, 'utf-8')
>>> print(open("foo.py").read()) # contents of file (now fixed)
a == b
网友评论