Python编码规范
2018.6
1. 缩进
- (垂直隐式缩进)对准左括号。
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)
- 多行结构中的大括号、中括号、小括号的右括号可与内容对齐单独起一行作为最后一行的第一个字符。
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',
)
- 当if语句的条件判断部分长到需要换行写的时候,注意可以在if后面增加一个空格,再增加一个左括号来创造一个4空格缩进的多行条件。如下是if语句推荐使用的三种缩进形式。
# 没有额外的缩进,但条件判断的语句会和后面的语句产生视觉冲突
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 增加一个注释,在能提供语法高亮的编辑器中可以有一些区分
if (this_is_one_thing and
that_is_another_thing):
# 这里添加注释,区分前后两部分的语句
do_something()
# 在条件判断的语句添加额外的缩进,和后面语句区分
if (this_is_one_thing
and that_is_another_thing):
do_something()
2. 换行
- 每行代码控制在120个字符以内(文档/注释限制在72个字符以内)。优先选择在小括号、中括号以及大括号中的隐式续行方式,比如通过小括号内表达式的换行方式将长串折成多行,如下所示。
query_str = ("select DriveLetter "
"from Win32_Volume "
"where Label = \"%s\"") % (args.logusn)
- 如果如无法使用隐式续行,则使用反斜杠 \ 续行,如下所示。
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())
- 公式应该在二元运算符之前中断换行,即换行之后的续行一开始是二元运算符,这样运算符和操作数容易进行匹配,如下所示。
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
- 不要使用复合语句,即同一行中存在多个语句,错误如下所示。
if foo == 'something': do_blah_thing()
do_one(); do_two(); do_three()
3. 空行
- 模块级函数和类定义之间空两行,类成员函数之间空一行,在函数中使用空行来区分逻辑段(谨慎使用)。
class Common(object):
"""common class,提供通用的方法"""
def __init__(self):
"""common的构造器:"""
pass
def method1(self):
pass
def method2(self):
pass
def method3():
pass
4. Imports导入
- 各个import应该独立成行。
# 正确
import os
import sys
# 错误
import os, sys
import总是位于文件的顶部,在模块注释和文档字符串之后,在模块的全局变量与常量之前。
-
应按照以下顺序import,且各import类型之间需要用空行隔开。
- 标准库
- 第三方库
- 本地库
应避免使用通配符进行import,即from xxx import *,建议使用如下形式。
from subprocess import Popen, PIPE
- 像__all__ , __author__ , __version__ 等这样的模块级属性,应该放在文档字符串的后面,以及除from
__future__ 之外的import表达式前面。
"""This is the example module.
This module does stuff.
"""
from __future__ import barry_as_FLUFL
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'John'
import os
import sys
- 包(package)内的模块导入:
包是一个含有多个模块文件的文件夹,Python2需要在这个文件内添加一个__init__.py文件,该文件内容可以为空,Python3可加可不加__init__.py,但为了规范最好加上。包内的模块文件不能作为顶层模块来执行,即不能作为主函数的入口。
包内的模块文件避免使用隐式相对导入同目录下的其它模块(Python3禁止使用)。建议在包内使用相对导入,在包外使用绝对导入。
package
├── __init__.py
├── sub_pkg1
│ ├── __init__.py
│ ├── module_x.py
│ ├── module_y.py
└── sub_pkg2
├── __init__.py
└── module_z.py
# module_x.py
import module_y # 隐式相对导入
from module_y import var # 隐式相对导入
from . import module_y # 显式相对导入
from .module_y import var # 显式相对导入
from sub_pkg1 import module_y # 绝对导入
5. 表达式和语句中的空格
-
如下二元运算符两边要放置一个空格:
- 赋值( = )
- 增量赋值( += , -=等 )
- 比较运算( == , < , > , != , <> , <= , >= , in , not in , is , is not )
- 布尔运算( and , or , not )
如果使用具有不同优先级的运算符,请考虑在具有最低优先级的运算符周围添加空格。有时需要通过自己来判断,但是不要使用一个以上的空格,并且在二元运算符的两边使用相同数量的空格。
# 正确写法
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)
- 函数参数列表中,逗号之后要有空格,但在制定关键字参数或者默认参数值的时候,不要在 = 附近加上空格。
# 正确写法
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# 错误写法
def complex(real,imag = 0.0):
return magic(r = real, i = imag)
-
其他注意事项:
- 一行的尾部不要有空格。
- 紧接着圆括号、方括号和花括号的内侧不加空格。
# 正确写法 spam(ham[1], {eggs: 2}) # 错误写法 spam( ham[ 1 ], { eggs: 2 } )
- 逗号及后面的括号之间不加空格。
# 正确写法 foo = (0,) # 错误写法 bar = (0, )
- 逗号,分号或冒号之前不加空格。
# 正确写法 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]
- 函数调用参数列表的圆括号前不加空格。
# 正确写法 spam(1) # 错误写法 spam (1)
- 索引或切片的方括号前不加空格。
# 正确写法 dct['key'] = lst[index] # 错误写法 dct ['key'] = lst [index]
- 在赋值语句的运算符周围,不要为了对齐而使用多个空格。
# 正确写法 x = 1 y = 2 long_variable = 3 # 错误写法 x = 1 y = 2 long_variable = 3
6. 注释
-
块注释
块注释通常在代码前,并和代码有同样的缩进。每行以#开头, 而且#后面有单个空格。块注释内部的段落通过只有一个#的空行分隔。
# 块注释段落1
#
# 块注释段落2
#
# 块注释段落3
下面是一个py文件开头块注释的格式。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# title :backup_full.py
# description :MySQL全量备份
# author :张雪峰
# date :20180601
# version :1.0
# notes :
# python_version :3.6
#==============================================================================
-
行内注释
#前至少使用两个空格和代码语句分开,#后面有单个空格,注意避免使用无意义的注释。
x = x + 1 # 行内注释
-
文档说明
要为所有的公共模块,函数,类以及方法编写文档说明。- 多行的文档说明,使用的结尾三引号应该自成一行。
"""This is the example module. This module does stuff. """
- 单行的文档说明,尾部的三引号应该和文档在同一行。
def mount(args): '''mount to new mysql instance''' target_dir = args.target_dir
- 公共方法的文档说明要注明入参和返回值。
def backup_alone(self, args): """单通道备份函数 Parameters: filesystem(str): 文件系统 mountpoint(str): 挂载点 include(str): 包含文件 exclude(str): 排除文件 Returns: pid(str): 进程号 """
- 非公共的方法没有必要写文档说明,但是应该有一个描述方法具体作用的注释,这个注释应该在def那一行之后。
def backup_synth(args): # 增量备份 logger.info('backup_synth')
7. 命名
-
模块和包
模块名应该简短,全部用小写字母,多字母之间可以使用单下划线连接。
包(通常是包含__init__.py及其他模块文件的目录文件)名也应该使用简短全小写的名字,但不建议用下划线。
package
├── __init__.py
├── sub_pkg1
│ ├── __init__.py
│ ├── module_x.py
│ ├── module_y.py
└── sub_pkg2
├── __init__.py
└── module_z.py
-
类
类名使用驼峰命名风格,首字母大写,私有类用_开头。
class BackupFile(object):
"""Backup_File class"""
-
异常
异常也是类,所以也使类名规则,但是应该增加后缀Error。
class SomeCustomError(Exception):
def __init__(self,str_length):
super(SomeCustomError,self).__init__()
self.str_length = str_length
try:
s = raw_input("输入一行字符串:\n")
# 输入字符串长度超过指定长度范围,引发异常
if (length < len(s)):
raise SomeCustomError(length)
except SomeCustomError as x:
print("捕获自定义异常")
-
全局变量
全局变量名应尽量只在模块内部使用, 对可能使用语句 from moduleName import variableName 而被导入的模块,应采用 __all__ 机制来防止全局变量被别的模块导入, 或者在全局变量名开头加一个前置下划线。
__all__ = ['var1', 'var2', 'var3']
_name = 'var4'
-
函数和变量
函数和变量名一律小写,如果想提高可读性可以用下划线分隔。
status_code = 0
def thread_fun(self, cmdstr):
"""单个线程"""
-
方法参数
类的方法第一个参数必须是self,而类方法第一个参数必须是cls。
class Kls(object):
no_inst = 0
def __init__(self):
Kls.no_inst = Kls.no_inst + 1
@classmethod
def get_no_of_instance(cls):
return cls.no_inst
-
常量
常量命名使用全部大写的方式,可以使用下划线。
HOST = "192.168.1.10"
PORT = 7777
NUM_LOG = 100000
8. 异常
- 自定义异常类应从Exception继承,而不是BaseException。
class SomeCustomError(Exception):
def __init__(self,str_length):
super(SomeCustomError,self).__init__()
self.str_length = str_length
- 当给捕捉的异常绑定一个名字时,使用在Python2.6中加入的as的语法。
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
- except后面需要指定捕捉的异常,裸露的except会捕捉所有异常,意味着会隐藏潜在的问题,可以有多个except语句,捕捉多种异常,分别做异常处理。
# 正确写法
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
# 错误写法
try:
import platform_specific_module
except:
platform_specific_module = None
- try/except子句中的语句不要太多,只在可能发生异常的地方使用,以免屏蔽掉其他的错误:
# 正确写法
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
# 错误写法
try:
return handle_value(collection[key])
except KeyError:
# 可能会捕获handle_value()引发的KeyError,而不是collection的
return key_not_found(key)
9. 接收和返回
-
接收
- 短选项:一个字符表示(区分大小写),以 - 为前缀。后面有 : 的字符表示该选项需要参数。传递参数的时候,要用 - 作为前缀,加上相应选项字符,该选项如果有参数的话,直接或空格后跟在后面。如 -h, 即命令行选项为 h, 如 -p80 或 -p 80, 即命令行选项为 p, 参数为 80。
- 长选项:一个字符串表示,以 -- 为前缀。后面有 = 的字符串表示该选项需要参数。传递参数的时候,要用 -- 作为前缀,加上相应选项字符串,该选项如果有参数的话,用 = 或空格加上参数跟在后面。如 --help, 即命令行选项为 help, 如 --port=80 或 --port 80, 即命令行选项为 port, 参数为 80。
-
返回
- 返回普通请求
# {"code":"错误码/正确为0", "msg":"具体错误/成功信息"} {"code":"0", "msg":"New storage successfully"} {"code":"1001", "msg":"The storage offline"}
- 返回展示数据请求
# {"code":"错误码/正确为0", "msg":"具体错误/成功信息", "data":[{"具体展示的数据1", "具体展示的数据2"}]} {"code":"0", "msg":"New storage successfully", "data":[{"storage1", "storage2"}]}
10. 其他
Linux下运行的脚本开头要添加注释申明Python解释器的位置,推荐使用 #!/usr/bin/env python,而不是 #!/usr/bin/python。
默认使用utf-8编码规则,在文件头部(Python解释器位置声明之后)添加 #-*-coding:utf-8-*- 标识。
和像None这样的单例对象进行比较的时候应该始终用 is 或者 is not,永远不要用 == 运算符。
用 is not 代替 not … is,前者的可读性更好。
如果一个资源是一段代码本地使用的,应使用with语句保证这个资源使用完后被正确释放, try...finally 语句也可以。
with open("/document/foo.txt") as f:
data = f.read()
try:
f = open("/document/foo.txt", "r")
except IOError as e:
print(e)
finally:
f.close()
- 函数或者方法在没有返回值时要明确返回None。
# 正确写法
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)
# 错误写法
def foo(x):
if x >= 0:
return math.sqrt(x)
def bar(x):
if x < 0:
return
return math.sqrt(x)
- 使用 .startswith() 和 .endswith() 而非字符切片去检测前缀或后缀。
# 推荐写法
if foo.startswith('bar'):
pass
# 不推荐写法
if foo[:3] == 'bar':
pass
- 使用 isinstance() 代替对象类型的比较。
# 推荐写法
if isinstance(obj, int):
pass
# 不推荐写法
if type(obj) is type(1):
pass
- 不要用 == 和布尔值进行比较。
# 推荐写法
if greeting:
pass
# 不推荐写法
if greeting == True:
pass
# 不推荐写法
if greeting is True:
pass