代码风格规范
一些代码风格笔记
前两天写 sql
的时候,被几个自动格式化的几个缩进逼疯了。我也突然意识到代码风格对我们编程的时候到底有多大的影响。我平时写个人项目偏多,也经常被一些难看的代码格式弄得好难受,看别人的代码也经常觉得代码过于丑陋,很多空格和缩进上的不同使得团队协作非常不方便。
这篇指南参考了一些专业团队的代码协作方式,算是给代码样式做一个学习笔记。下面的内容主要是我平时遇到的一些规范。详细的内容可以参考相应的官网。
Python 风格指南
原文参考Python 风格指南。
很多团队都会使用 Black 和 Pying 作为自动格式化的工具(我平时使用的就是 Black)
Python 语言规范
Lint
pylint 是在 Python 代码中寻找 bug 和格式问题的工具。但 pylint 并不完美,有些死板的警告并不适合我们灵活的代码,有时候就需要我们手动进行抑制,或者直接忽略。例如:
def do_PUT(self): # WSGI 接口名,所以 pylint: disable=invalid-name
异常
定义:异常时一种跳出正常的控制流,以处理错误或其他异常情况的方法。
永远不要用 except: 语句来捕获所有异常,因为这种写法会使得 Python 变得非常宽容。相应的,try/except
中的代码量也应该尽量小,避免真正的错误被掩盖。
全局变量
定义:在程序运行时可以发生变化的模块级变量和类属性。
避免使用全局变量。在特殊情况下需要用到全局变量时,应将全局变量声明为模块级变量或者类属性,并在名称前加 _ 以表示为内部状态。如需从外部访问全局变量,必须通过公有函数或类方法实现。
嵌套/局部/内部类和函数
可以谨慎使用. 尽量避免使用嵌套函数和嵌套类, 除非需要捕获 self 和 cls 以外的局部变量. 不要仅仅为了隐藏一个函数而使用嵌套函数.
应将需要隐藏的函数定义在模块级别, 并加上 _ 作为标注.
默认迭代器和操作符
优先使用返回迭代器的方法, 而非返回列表的方法, 不过注意使用迭代器时 不能修改容器.
正确:
for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
错误:
for key in adict.keys(): ...
for line in afile.readlines(): ...
函数与方法装饰器
定义: 装饰器作用在函数和方法上.
仅在有显著优势时, 审慎地使用装饰器. 不得使用 staticmethod, 除非为了兼容老代码库地 API 不得已而为之. 应把静态方法改写为模块级函数.
例如:
正确:
# utils.py
def to_snake_case(name: str) -> str:
return name.replace("-", "_").lower()
# 调用方式
from utils import to_snake_case
result = to_snake_case("Hello-World")
错误:
# utils.py
class StringUtils:
@staticmethod
def to_snake_case(name: str) -> str:
return name.replace("-", "_").lower()
# 调用方式
from utils import StringUtils
result = StringUtils.to_snake_case("Hello-World")
这是因为, 如果一个函数不依赖于类的状态, 实际上可以提取出来, 不放在类里面实现.
Python 风格规范
分号
不要在行尾加分号, 也不要用分号将两条语句合并到一行 (C++工程师暴怒)
行宽
最大行狂是 80 个字符. 当然也有例外, 比如一些 URL, 路径名以及长的标志. 不要用反斜杠表示显式续航. 应该利用 Python 的圆括号, 中括号和花括号的隐式续行.
括号
使用括号时宁缺毋滥. 可以把元组括起来, 但不强制. 不要在返回语句或条件语句中使用括号, 除非用于隐式续航或者元组.
缩进
用 4 个空格作为缩进. 不要使用制表符. 使用隐式续行时, 应该把括起来的元素垂直对齐, 或者添加 4 个空格悬挂缩进.
注释和文档字符串 (docstring)
模块
每个文件应该包含一个许可协议模板. 文件的开头是文档字符串,其中应该描述模块内容和用法.
"""模块或程序的一行概述, 以句号结尾.
留一个空行. 接下来应该写模块或程序的总体描述. 也可以选择简要描述导出的类和函数,
和/或描述使用示例.
经典的使用示例:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
函数和方法
一个函数的基本内容应包括:
- 基本说明:
- Args: (可以换行)
- Returns:
- Raises:
例如:
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[bytes | str],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""从 Smalltable 获取数据行.
从 table_handle 代表的 Table 实例中检索指定键值对应的行. 如果键值是字符串,
字符串将用 UTF-8 编码.
参数:
table_handle: 处于打开状态的 smalltable.Table 实例.
keys: 一个字符串序列, 代表要获取的行的键值. 字符串将用 UTF-8 编码.
require_all_keys: 如果为 True, 只返回那些所有键值都有对应数据的
行.
返回:
一个字典, 把键值映射到行数据上. 行数据是字符串构成的元组. 例如:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
返回的键值一定是字节串. 如果字典中没有 keys 参数中的某个键值, 说明
表格中没有找到这一行 (且 require_all_keys 一定是 false).
抛出:
IOError: 访问 smalltable 时出现错误.
"""
类
应包括 Attributes (属性)
例如:
class SampleClass(object):
"""这里是类的概述.
这里是更多信息....
这里是更多信息....
属性:
likes_spam: 布尔值, 表示我们是否喜欢午餐肉.
eggs: 用整数记录的下蛋的数量.
"""
def __init__(self, likes_spam = False):
"""用某某某初始化 SampleClass."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""执行某某操作."""
字符串
应使用 f-string, % 运算符或 format 方法来格式化字符串. 可以用 + 实现单词拼接, 不能用来实现格式化
导入
导入语句也有一定顺序:
- 导入 Python 的
__future__. - 导入 Python 的标准库
- 导入第三方模块和包
- 导入代码仓库中的子包
- 已废弃的规则: 导入应用专属的\与该文件属于同一个子包的模块.
命名:
- 模块名:
module_name; - 包名:
package_name; - 类名:
ClassName; - 方法名:
method_name; - 异常名:
ExceptionName; - 函数名:
function_name,query_proper_noun_for_thing,send_acronym_via_https; - 全局常量名:
GLOBAL_CONSTANT_NAME; - 全局变量名:
global_var_name; - 实例名:
instance_var_name; - 函数参数名:
function_parameter_name; - 局部变量名:
local_var_name.