python 代码规范
之前做过一些代码行数较多的项目,由于没有注意代码规范(如变量、函数命名的规范),导致项目代码非常混乱。如今也要逐渐步入科研生活,在科研生活代码实践的开始,我认为进行 python 代码规范的学习是非常有必要的(尤其是我有很严重的代码洁癖)。看到好的代码会让自己舒心,也会让别人舒心。
本篇博客将会大幅度 copy google 开源项目风格指南 中的内容,仅提取其中我认为比较重要的部分,手打一遍,增加印象。对于想要认真学习 python 代码规范的同学,还是建议转去 google 开源项目风格指南 进行学习,里边提供了非常详尽的规范以及样例。
行长度
每行尽可能不能超过 80 个字符,除非是 with
语句需要三个以上的上下文管理器,否则不要使用反斜杠连接行。Python 中圆括号、中括号和花括号能够将行隐式的连接起来,可以利用该特性管理换行。
foo_bar(self, width, weight, color="black", design=None,
x="foo", emphasis=None)
if (width == 0 and height == 0 and
color == "red" and emphasis == "strong")
x = ("This will build a very long long "
"long long long long string")
对于 with
表达式,若有三个及以上的上下文管理器,可以使用反斜杠换行,若只有两个,不使用反斜杠,使用嵌套的 with
with very_long_first_expr() as spam, \
very_long_second_expr() as beans, \
third_thing() as eggs:
something()
with very_long_first_expr() as spam:
with very_long_second_expr() as beans:
something()
括号
宁缺毋滥地使用括号,不要在条件语句或返回值中使用,括号可用于实现行连接(如上)或者元组类型。
缩进
不要使用 tab
,使用 4 个空格来缩进代码。对于行连接,可以使用垂直对齐换行,也可以使用4空格的悬挂式缩进(第一行不应该有参数)。
foo = long_function_name(var_one, var_two
var_three, var_four)
foo = long_function_name(
var_one, var_two, var_three,
var_four
)
序列元素尾部逗号
当 ]
、)
、}
和末位元素不在同一行时,推荐使用序列元素尾部逗号
golomb3 = [0, 1, 3]
golomb4 = [
0,
1,
4,
6,
]
空行
顶级定义之间空两行,方法定义之间空一行。顶级定义指函数定义和类定义。
空格
使用 =
指示关键字参数或默认参数值时,不在 =
两侧使用空格。若存在类型注释,需要在 =
两侧使用空格。
def fun(real, img=0.0): return magic(c=real, i=img)
def fun(real, img: float = 0.0): return magic(c=real, i=img)
不需要使用空格进行对行的垂直对齐,否则后期维护比较困难。
foo = 1000 # comment
long_name = 2 # comment that should not be aligned
dictionary = {
"foo": 1,
"long_name": 2,
}
Shebang
大部分 .py
文件无需以 #!
开头,main.py
文件需以 #!/usr/bin/python2
或 #!/usr/bin/python3
开始
注释
Python 能够使用文档字符串自动生成注释。文档字符串是指包、模块、类或函数里的第一个语句。一个文档字符串中,应首先是一行以句号、问号或感叹号结尾的概述,随后是一个空行,随后是剩下的部分。
模块
每个文件应包含一个许可样本,根据项目使用的许可(例如, Apache 2.0, BSD, LGPL, GPL), 选择合适的样板,其开头是对模块内容和用法的描述。
"""A one line summary of the module or program, terminated by a period.
Leave one line above. The rest of this docstring should contain an
overall description of the module or program. Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.
Typical usage example:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
函数和方法
一个函数必须有文档字符串,除非满足:
- 外部不可见
- 非常短小
- 简单明了
文档字符串包括函数做了什么,输入,输出的详细描述。覆盖基类的子类方法应有一个类似 See base class
的简单注释。
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetches row from a Smalltable
Retrieves rows pertaining to the given keys from ...
Args:
table_handle: xxx
keys: xxx xxx xxx xxx xxx xxx
xxx xxx
require_all_keys: xxx xxx xxx
xxx xxx xxx
Returns:
A dict mapping keys to the xxx xxx xxx
xxx xxx xxx. For examples:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
xxx xxx xxx
Raises:
IOError: An error occurred accessing the smalltable.
"""
类
类应该在其定义下有一个用于描述该类的文档字符串,若类有公共属性,则文档中应有属性段。
class SampleClass(object):
"""Summary of class here.
Longer class information...
Longer class information...
Attributes:
likes_spam: xxx
eggs: xxx
"""
def __init__(self, likes_spam=False):
"""Inits sampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0
xxx
块注释和行注释
不要描述代码,而是说明代码的作用
类
若一个类不继承自其他类,需要显示的从 objec
继承。继承自 object
是为了使属性(properties)正常工作, 并且这样可以保护你的代码, 使其不受 PEP-3000 的一个特殊的潜在不兼容性影响. 这样做也定义了一些特殊的方法, 这些方法实现了对象的默认语义, 包括
字符串
判断使用 %
或 +
操作符来格式化字符串。
# 正确的
x = a + b
x = "%s, %s!" % (str1, str2)
x = "{}, {}!".format(str1, str2)
x = "name: %s; score: %d" % (name, n)
x = "name: {}; score: {}".format(name, n)
# 错误的
x = "%s%s" % (str1, str2)
x = "name: " + name + "; score: " + n
避免在循环中使用 +
或者 +=
来累加字符串,可以使用 .join
连接列表
items = []
for x, y in lst:
items.append("(%d, %d)" % (x, y))
final_str = "".join(items)
尽可能在同一个文件中保持引号的一致性。
TODO 注释
为临时代码使用 TODO 注释。开头处包含 TODO
字符串,随后用括号括起来名字或邮件等标识符,随后是可选冒号,随后是一行注释,解释要做什么。
# TODO(jcyoung): Change this to use relations.
导入格式
每个导入独占一行(typing
除外),不导入函数,导入函数所在的包。导入应位于文件顶部,位于模块注释和文档字符串之后,模块全局变量和常量之前。导入应按照从最通用到最不通用的顺序分组。
- 从
__future__
导入 - 标准库导入
- 第三方库导入
- 本地代码子包导入
在每个分组中,应根据每个模块的完整包路径按字典序排序,忽略大小写
import collections
import queue
import sys
from absl import app
from absl import flags
import bs4
import cryptography
import tensorflow as tf
from book.genres import scifi
from myproject.backend import huxley
from myproject.backend.hgwells import time_machine
from myproject.backend.state_machine import main_loop
from otherproject.ai import body
from otherproject.ai import mind
from otherproject.ai import soul
命名
- 模块名写法:
module_name
- 包名写法:
package_name
- 类型名:
ClassName
- 方法名:
method_name
- 异常名:
ExceptionName
- 函数名:
function_name
- 全局常量名:
GLOBAL_CONSTANT_NAME
- 全局变量名:
global_var_name
- 实例名:
instatnce_var_name
- 函数参数名:
function_parameter_name
- 局部变量名:
local_var_name
约定:
内部
表示仅模块内可用,或者类内是保护或私有的- 用单下划线表示模块变量或函数是
protected
的 - 用双下划线
__
开头的实例变量或方法表示类内私有 - 没有必要限制一个类一个模块,将相关的类和顶级函数放在同一个模块中
- 模块名使用小写加下划线的方式
类型注释
占坑,下周会专门进行类型注释的学习