此编码风格指南主要基于 Google Python Style Guide [中译版],结合百度python使用习惯和实际开发情况制定。
1. 语言规范
1.1 import
[强制] 禁止使用from xxx import yyy语法直接导入类或函数(即yyy只能是module或package,不能是类或函数)。
[强制] 禁止使用from xxx import *
[强制] import时必须使用package全路径名(相对PYTHONPATH),禁止使用相对路径(相对当前路径)。
1.2 异常
[强制] 除非重新抛出异常,禁止使用except:捕获所有异常。
[强制] 捕捉异常时,应当使用as语法,禁止使用逗号语法。
[强制] 禁止使用双参数形式(raise MyException, 'Error Message')或字符串形式(raise 'Error Message')语法抛异常。
# 正确 try: …… except Exception as e: raise MyException('Error Message!') # 错误 raise MyException, 'Error Message'raise 'Error Message'
[强制] 如果需要自定义异常,应该在模块内定义名为Error的异常基类。该基类必须继承自Exception。其它异常类都从该Error类派生而来。
class Error(Exception): def __init__(self,err='XXX错误'): Exception.__init__(self,err) class DatabaseException(Error): def __init__(self,err='数据库错误'): DatabaseException.__init__(self,err)
====
[建议] 可以使用异常。但使用前请务必详细了解异常的行为,谨慎使用。
[建议] 除非重新抛出异常,否则不建议捕获Exception或StandardError。如果捕获,必须在日志中记录所捕获异常信息。
[建议] 建议try中的代码尽可能少。避免catch住未预期的异常,掩藏掉真正的错误。
[建议] 建议使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码,这对于清理资源常常很有用。例如:文件关闭。
1.3 全局变量
[强制] 禁止使用全局变量。除了以下例外:
- 脚本默认参数
- 模块级常量
[强制] 如果定义全局变量,必须写在文件头部。
1.4 构造函数
[建议] 类构造函数应该尽量简单,不能包含可能失败或过于复杂的操作。
1.5 函数返回值
·[强制] 函数返回值必须小于等于3个。
3个以上时必须通过class/namedtuple/dict等具名形式进行包装。
## Ex1 def get_numbers(): return 1, 2, 3 a, b, c = get_numbers() ## Ex2 class class Person(object): def __init__(self, name, gender, age, weight): self.name = name self.gender = gender self.age = age self.weight = weight ## Ex3 namedtuple import collections Person = collections.namedtuple('Person', 'name gender age weight') ## Ex4 dict def get_person_info(): return Person('jjp', 'MALE', 30, 130) person = get_person_info()
1.6 嵌套/局部/内部类或函数
[建议] 不推荐使用嵌套/局部/内部类或函数。
1.7 列表推导
[强制] 可以使用列表推导。mapping、loop、filter部分单独成行,且最多只能写一行。禁止多层loop或filter。
(解释:复杂的列表推导难以理解,建议转换成对应的for循环)
1.8 默认迭代器和操作符
·[强制] 对容器或文件的只读遍历,应该使用内置的迭代方法,不要使用返回list的方式遍历。
·[强制] [PY013] 对容器类型,使用in或not in判断元素是否存在。而不是has_key。
# ============ YES ============ for key in adict: .... if key not in adict: .... if obj in alist: .... for line in afile: .... for k, v in dict.iteritems(): .... # 删除毕业学生 for id in students.keys(): if students[id].graduated: del students[id] # ============ No ============ for key in adict.keys(): .... if not adict.has_key(key): .... for line in afile.readlines(): .... #删除毕业学生 for id in students: if students[id].graduated: del students[id] # 抛出RuntimeError异常
1.9 生成器
·[建议] 当返回较长列表数据时建议使用yield和generator函数。
#返回n以内的奇数 def odds(n): for i in xrange(1, n + 1): if i % 2 == 1: yield i for i in odds(1000): print i # ============================ def odds(n): ret = [] for i in xrange(1, n + 1): if i % 2 == 1: ret.append(i) return ret for i in odds(1000): print i
1.10 lambda函数
·[强制] 可以使用lambda函数,但仅限一行之内。
1.11 条件表达式
[强制] 条件表达式仅用于一行之内,禁止嵌套使用
1.12 默认参数
[强制] 仅可使用以下基本类型字面常量或常量作为默认参数:整数、bool、浮点、字符串、None
(解释:以可修改的对象(如list、dict、object等)作为默认参数,可能会被不小心改掉,导致默认值发生变化,产生难以追查的错误)
def foo(a, b=None): if b is None: b = []
1.13 属性(properties)
property() 函数的作用是在新式类中返回属性值。
[强制] 可以使用property。但禁止在派生类里改写property实现。
(解释:由于property是在基类中定义的,默认绑定到基类的实现函数。
若允许在派生类中改写property实现,则需要在基类中通过间接方式调用property实现函数。
这个方法技巧性太强,可读性差,所以禁止使用。)
import math class Square(object): """ [A square with two properties: a writable area and a read-only perimeter.] To use: >>> sq = Square(3) >>> sq.area 9 >>> sq.perimeter 12 >>> sq.area = 16 >>> sq.side 4 >>> sq.perimeter 16 """ def __init__(self, side): self.side = side def __get_area(self): '''Calculates the 'area' property.''' return self.side ** 2 def __set_area(self, area): '''Sets the 'area' property.''' self.side = math.sqrt(area) def __del_area(self, area): '''Del the 'area' property.''' del self.side area = property(__get_area, __set_area, doc="""Gets or sets the area of the square.""") @property def perimeter(self): return self.side * 4
1.14 True/False求值
[强制] 禁止使用==或!=判断表达式是否为None,应该用is或is not None
[强制] 当明确expr为bool类型时,禁止使用==或!=与True/False比较。应该替换为expr或not expr
[强制] 判断某个整数表达式expr是否为零时,禁止使用not expr,应该使用expr == 0
[建议] 建议显式转换到bool类型,慎用到bool类型的隐式转换。如使用隐式转换,你需要确保充分了解其语义
if users is None or len(users) == 0: print 'no users'
foo = True if not foo: self.handle_zero() if i % 10 == 0: self.handle_multiple_of_ten()
2. 风格规范
2.1 分号
[强制] 禁止以分号结束语句
[强制] 一行只能写一条语句,没有例外情况
2.2 行列长度
[强制] 每行不得超过100个字符
[强制] 函数长度不得超过100行
2.3 缩进
·[强制] 使用4个空格缩进,禁止使用tab缩进。
·[强制] 把单行内容拆成多行写时,要么与首行保持对齐;要么首行留空,从第二行起统一缩进4个空格;为与后面的代码区分,可以使用8空格缩进。
(解释:不同编辑器对TAB的设定可能不同,使用TAB容易造成在一些编辑器下代码混乱,所以建议一率转换成空格。)
# Aligned with opening delimiter foo = long_function_name(var_one, var_two, var_three, var_four) # 4-space hanging indent in a dictionary foo = { long_dictionary_key: long_dictionary_value, ... }
2.4 空行
[强制] 文件级定义(类或全局函数)之间隔两个空行,类方法之间隔一个空行
2.5 空格
[强制] 圆括号、方括号、花括号内侧都不加空格
spam(ham[1], {eggs: 2}, [])
[强制] 参数列表, 索引或切片的左括号前不应加空格
dict['key'] = list[index]
[强制] 逗号、分号、冒号前不加空格,后边加一个空格
if x == 4: print x, y x, y = y, x
[强制] 所有二元运算符前后各加一个空格
x == 1
[强制] 关键字参数或参数默认值里的等号前后不加空格
def complex(real, imag=0.0): return magic(r=real, i=imag)
2.6 注释
[强制] 使用文档字符串(docstring)描述module、function、class和method接口。docstring必须用三个双引号括起来。
[强制] 对外接口部分必须用docstring描述,内部接口视情况自行决定是否写docstring。
[强制] 接口的docstring描述至少包括功能简介、参数、返回值。如果可能抛出异常,必须注明。
[强制] 每个文件都必须有文件声明,文件声明必须包括以下信息:版权声明,功能和用途简介,修改人及联系方式。
"""[parse error message] Args: e (error): [error] Returns: str: [parsed error message] Raises: IOError: An error occurred accessing the bigtable.Table object. """
2.7 import格式
[强制] 每行只能导入一个库
- [强制] 必须按如下顺序排列
import
- ,每部分之间留一个空行
- 标准库
- 第三方库
- 应用程序自有库
import os import sys from third.party import lib from third.party import foobar as fb import my.own.module
2.8 命名规则
[强制] 类(包括异常)名使用首字母大写驼峰式命名
[强制] 常量使用全大写字母,单词间用下划线分隔
[强制] 其它情况(目录/文件/package/module/function/method/variable/parameter)一律使用全小写字母,单词间用下划线分隔
[强制] protected成员使用单下划线前缀,private成员使用双下划线前缀
[强制] 禁止使用双下划线开头,双下划线结尾的名字(类似__init__)
3. 编程实践
3.1 类继承
[强制] 如果一个类没有基类,必须继承自object类。
class SampleClass(object): pass class OuterClass(object): class InnerClass(object): pass class ChildClass(ParentClass): """Explicitly inherits from another class already."""
3.2 字符串格式化与拼接
[强制] 除了a+b这种最简单的情况外,应该使用%或format格式化字符串。
[强制] 不要使用+=拼接字符串列表,应该使用join。
x = '%s, %s!' % (imperative, expletive) x = '{}, {}!'.format(imperative, expletive) x = 'name: %s; score: %d' % (name, n) x = 'name: {}; score: {}'.format(name, n)
items = ['<table>'] for last_name, first_name in employee_list: items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name)) items.append('</table>') employee_table = ''.join(items)
3.3 文件和socket
[强制] 用完文件或socket后必须显式关闭句柄。建议使用with语法简化开发
with open("hello.txt") as hello_file: for line in hello_file: print line
3.4 主程序
[强制] 所有module都必须可导入。如需要执行主程序,必须检查__name__ == '__main__'
3.5 单元测试
[建议] 推荐使用PyUnit做单元测试。是否需要做单元测试以及目标单测覆盖率由项目负责人自行决定。
[建议] 推荐测试代码放在单独的test目录中。如果被测试代码文件名为xxx.py,那么测试代码文件应该被命名为xxx_test.py#
# math_test.py
# !/usr/bin/env python # -*- coding: gb18030 -*- import unittest import math class MathTestCase(unittest.TestCase): def test_sqrt(self): self.assertEqual(math.sqrt(4) * math.sqrt(4), 4) if __name__ == "__main__": unittest.main()
3.4 日志输出
[建议] 推荐使用python自带的logging库打印日志。
[建议] 推荐默认日志格式:"%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s", 时间格式:"%Y-%m-%d %H:%M:%S"
[建议] 推荐线上程序使用两个日志文件:一个专门记录warning/error/critical日志,另一个记录所有日志。
# log.py import os import logging import logging.handlers def init_log(log_path, level=logging.INFO, when="D", backup=7, format="%(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s", datefmt="%m-%d %H:%M:%S"): """ init_log - initialize log module Args: log_path - Log file path prefix. Log data will go to two files: log_path.log and log_path.log.wf Any non-exist parent directories will be created automatically level - msg above the level will be displayed DEBUG < INFO < WARNING < ERROR < CRITICAL the default value is logging.INFO when - how to split the log file by time interval 'S' : Seconds 'M' : Minutes 'H' : Hours 'D' : Days 'W' : Week day default value: 'D' format - format of the log default format: %(levelname)s: %(asctime)s: %(filename)s:%(lineno)d * %(thread)d %(message)s INFO: 12-09 18:02:42: log.py:40 * 139814749787872 HELLO WORLD backup - how many backup file to keep default value: 7 Raises: OSError: fail to create log directories IOError: fail to open log file """ formatter = logging.Formatter(format, datefmt) logger = logging.getLogger() logger.setLevel(level) dir = os.path.dirname(log_path) if not os.path.isdir(dir): os.makedirs(dir) handler = logging.handlers.TimedRotatingFileHandler(log_path + ".log", when=when, backupCount=backup) handler.setLevel(level) handler.setFormatter(formatter) logger.addHandler(handler) handler = logging.handlers.TimedRotatingFileHandler(log_path + ".log.wf", when=when, backupCount=backup) handler.setLevel(logging.WARNING) handler.setFormatter(formatter) logger.addHandler(handler)