Python

此编码风格指南主要基于 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)

====

[建议] 可以使用异常。但使用前请务必详细了解异常的行为,谨慎使用。

[建议] 除非重新抛出异常,否则不建议捕获ExceptionStandardError。如果捕获,必须在日志中记录所捕获异常信息。

[建议] 建议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] 对容器类型,使用innot 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,应该用isis not None

[强制]  当明确expr为bool类型时,禁止使用==!=True/False比较。应该替换为exprnot 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)
原文地址:https://www.cnblogs.com/blitheG/p/14652330.html