python 代码规范

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 除外),不导入函数,导入函数所在的包。导入应位于文件顶部,位于模块注释和文档字符串之后,模块全局变量和常量之前。导入应按照从最通用到最不通用的顺序分组。

  1. __future__ 导入
  2. 标准库导入
  3. 第三方库导入
  4. 本地代码子包导入

在每个分组中,应根据每个模块的完整包路径按字典序排序,忽略大小写

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
  • 用双下划线 __ 开头的实例变量或方法表示类内私有
  • 没有必要限制一个类一个模块,将相关的类和顶级函数放在同一个模块中
  • 模块名使用小写加下划线的方式

类型注释

占坑,下周会专门进行类型注释的学习