本文主要介绍python的编程规范
编码风格
Guido的一个重要观点是代码被读的次数远多于被写的次数。pep8主要用于保持编写的Python代码风格一致,但是很多三方库的大佬也没有完全遵守这套规范(可能是历史原因造成需要和之前的代码保持一致)。 但是绝大多数情况下,作为开发者一定要遵守这些规范,只有这样代码可读性更强也能为开源社区贡献自己的力量。
idea基本上能很大程度规范我们的代码,下面主要介绍代码的布局及规范的具体操作。
代码布局
缩进
每个缩进级别采用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)
# 续行并没有被区分开,需要再缩进一级
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
对于续行来说,4空格的规则可以不遵守。
# 悬挂缩进可以不采用4空格的缩进方法。
foo = long_function_name(
var_one, var_two,
var_three, var_four)
每行最大长度
将所有行都限制在79个字符长度以内。当代码仅仅只由一个团队维护时,可以达成一致让行长度增加到80到100字符(实际上最大行长是99字符),注释和文档字符串仍然是以72字符换行。
二元运算符之前还是之后换行?
长期以来一直推荐的风格是在二元运算符之后换行。但是这样会影响代码可读性,包括两个方面:一是运算符会分散在屏幕上的不同列上,二是每个运算符会留在前一行并远离操作数。所以,阅读代码的时候眼睛必须做更多的工作来确定哪些操作数被加,哪些操作数被减:
# 错误的例子:运算符远离操作数
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
# 正确的例子:更容易匹配运算符与操作数
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
空行
使用2个空行来分隔最外层的函数(function)和类(class)定义。
使用1个空行来分隔类中的方法(method)定义。
可以使用额外的空行(尽量少)来分隔一组相关的函数。在一系列相关的仅占一行的函数之间,空行也可以被省略(比如一组虚函数定义)。
在函数内使用空行(尽量少)使代码逻辑更清晰。
模块引用
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
不同组的imports之前用空格隔开。
推荐使用绝对(absolute)imports,因为这样通常更易读,在import系统没有正确配置(比如中的路径以sys.path结束)的情况下,也会有更好的表现(或者至少会给出错误信息):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
然而,除了绝对imports,显式的相对imports也是一种可以接受的替代方式。特别是当处理复杂的包布局(package layouts)时,采用绝对imports会显得啰嗦。
from . import sibling
from .sibling import example
避免使用通配符imports(from
模块级的双下划线命名
模块中的“双下滑线”(变量名以两个下划线开头,两个下划线结尾)变量,比如__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
字符串引用
在Python中表示字符串时,不管用单引号还是双引号都是一样的。但是不推荐将这两种方式看作一样并且混用。最好选择一种规则并坚持使用。当字符串中包含单引号时,采用双引号来表示字符串,反之也是一样,这样可以避免使用反斜杠,代码也更易读。
表达式和语句中的空格
在下列情形中避免使用过多的空白:
- 方括号,圆括号和花括号之后:
#正确的例子:
spam(ham[1], {eggs: 2})
#错误的例子:
spam( ham[ 1 ], { eggs: 2 } )
- 逗号,分号或冒号之前:
#正确的例子:
if x == 4: print x, y; x, y = y, x
#错误的例子:
if x == 4 : print x , y ; x , y = y , x
- 不过,在切片操作时,冒号和二元运算符是一样的,应该在其左右两边保留相同数量的空格(就像对待优先级最低的运算符一样)。在扩展切片操作中,所有冒号的左右两边空格数都应该相等。不过也有例外,当切片操作中的参数被省略时,应该也忽略空格。
#正确的例子:
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]
- 在调用函数时传递参数list的括号之前:
#正确的例子:
spam(1)
#错误的例子:
pam (1)
- 在索引和切片操作的左括号之前:
#正确的例子:
dct['key'] = lst[index]
#错误的例子:
dct ['key'] = lst [index]
- 赋值(或其他)运算符周围使用多个空格来和其他语句对齐:
#正确的例子:
x = 1
y = 2
long_variable = 3
#错误的例子:
x = 1
y = 2
long_variable = 3
注释
和代码矛盾的注释还不如没有。当代码有改动时,一定要优先更改注释使其保持最新。
块注释
块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。块注释的每一行都应该以#和一个空格开头(除非该文本是在注释内缩进对齐的)。
块注释中的段落应该用只含有单个#的一行隔开。
行内注释
尽量少用行内注释。 行内注释是和代码语句写在一行内的注释。行内注释应该至少和代码语句之间有两个空格的间隔,并且以#和一个空格开始。
文档字符串
所有的公共模块,函数,类和方法都应该有文档字符串。对于非公共方法,文档字符串不是必要的,但你应该留有注释说明该方法的功能,该注释应当出现在def的下一行。
多行文档字符串以单行”"”结尾,不能有其他字符,例如:
"""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(大驼峰):当CapWords里包含缩写时,将缩写部分的字母都大写。HTTPServerError比HttpServerError要好
- capitalizedWords(小驼峰)
- Capitalized_Words_With_Underscores (这种风格超丑!)
- _single_leading_underscore:以单个下划线开头是”内部使用”的弱标志。 比如, from M import *不会import下划线开头的对象。
- single_trailing_underscore_:以单个下划线结尾用来避免和Python关键词产生冲突。
- __double_leading_underscore:以双下划线开头的风格命名类属性表示触发命名修饰(在FooBar类中,__boo命名会被修饰成_FooBar__boo
- double_leading_and_trailing_underscore__:以双下划线开头和结尾的命名风格表示“魔术”对象或属性,存在于用户控制的命名空间(user-controlled namespaces)里(也就是说,这些命名已经存在,但通常需要用户覆写以实现用户所需要的功能)。 比如, __init, import 或 file。请依照文档描述来使用这些命名,千万不要自己发明。
规范:命名约定
- 需要避免的命名
不要使用字符’l’(L的小写的字母),’O’(o大写的字母),或者’I’(i的大写的字母)来作为单个字符的变量名。 在一些字体中,这些字符和数字1和0无法区别开来。比如,当想使用’l’时,使用’L’代替。
- ASCII兼容性
标准库中使用的标识符必须与ASCII兼容
- 包和模块命名
模块命名应短小,且为全小写。若下划线能提高可读性,也可以在模块名中使用。Python包命名也应该短小,且为全小写,但不应使用下划线。
- 类命名
类命名应该使用驼峰(CapWords)的命名约定。
- 类型变量命名
建议将后缀_co或_contra添加到用于声明相应的协变(covariant)和逆变(contravariant)的行为。例如:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
- 异常命名
使用CapWords+Error后缀的方式
- 全局变量命名
(在此之前,我们先假定这些变量都仅在同一个模块内使用。)这些约定同样也适用于函数命名。
对于引用方式设计为from M import *的模块,应该使用__all__机制来避免import全局变量,或者采用下划线前缀的旧约定来命名全局变量,从而表明这些变量是“模块非公开的”。
- 函数命名
函数命名应该都是小写,必要时使用下划线来提高可读性。
- 函数和方法参数
实例方法的第一参数永远都是self。
类方法的第一个参数永远都是cls。\
- 常量
常量通常是在模块级别定义的,使用全部大写并用下划线将单词分开。如:MAX_OVERFLOW和TOTAL 。
编程建议
代码应该以不影响其他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来看待。
用is not操作符而不是not … is。
始终使用def语句来代替直接绑定了一个lambda表达式的赋值语句。
异常类应派生自Exception而不是BaseException。直接继承BaseException是为Exception保留的,从BaseException继承并捕获异常这种做法几乎总是错的。
捕获异常时,尽可能使用明确的异常,而不是用一个空的except:语句。
用’‘.startswith()和’‘.endswith()代替字符串切片来检查前缀和后缀。
对象类型的比较应该始终使用isinstance()而不是直接比较。
对于序列(字符串、列表、元组)来说,空的序列为False:
#正确写法:
if not seq:
if seq:
#错误写法:
if len(seq):
if not len(seq):
- 不要用==比较True和False。
#推荐的写法:
if greeting:
if not greeting:
#不推荐的写法:
if greeting == True:
#更加不推荐的写法:
if greeting is True: