Python编码规范

1. Python之禅

"""
The Zen of Python

Beautiful is better than ugly. 优美胜于丑陋。
Explicit is better than implicit. 显式胜于隐式。 Simple is better than complex. 简单胜于复杂。 Complex is better than complicated. 复杂胜于难懂。 Flat is better than nested. 扁平胜于嵌套。 Sparse is better than dense. 分散胜于密集。 Readability counts. 可读性应当被重视。 Special cases aren’t special enough to break the rules. Although practicality beats purity. 尽管实用性会打败纯粹性,特例也不能凌驾于规则之上。 Errors should never pass silently. Unless explicitly silenced. 除非明确地使其沉默,错误永远不应该默默地溜走。 In the face of ambiguity, refuse the temptation to guess. 面对不明确的定义,拒绝猜测的诱惑。 There should be one– and preferably only one –obvious way to do it. 用一种方法,最好只有一种方法来做一件事。 Although that way way not be obvious at first unless you’re Dutch. 虽然一开始这种方法并不是显而易见的,但谁叫你不是Python之父呢。 Now is better than never. Although never is often better than right now. 做比不做好,但立马去做有时还不如不做。 If the implementation is hard to explain, it’s a bad idea. 如果实现很难说明,那它是个坏想法。 If the implementation is easy to explain, it may be a good idea. 如果实现容易解释,那它有可能是个好想法。 Namespaces are one honking great idea – let’s do more of those! 命名空间是个绝妙的想法,让我们多多地使用它们吧!
-- by Tim Peters
"""

2. 语言规范

2.1. import

导入位置:导入始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。

导入顺序:标准库,相关的第三方库,本地库。

 

导入要在一行

Yes:

import os
import sys
from subprocess import Popen, PIPE

No:

import sys, os

 

as的使用 from x import y as z, 如果两个要导入的模块都叫做y或者y太长了.

 

禁止使用通配符导入

通配符导入(from <module> import *)应该避免,因为它不清楚命名空间有哪些名称存,混淆读者和许多自动化的工具。唯一的例外是重新发布对外的API时可以考虑使用。

 

 

2.2. 异常

·[强制] 如果需要自定义异常,应该在模块内定义名为Error的异常基类。该基类必须继承自Exception。其它异常类都从该Error类派生而来。

·[强制] 除非重新抛出异常,禁止使用except:捕获所有异常。

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

·[强制] [PY007] 捕捉异常时,应当使用as语法,禁止使用逗号语法。

·[建议] 建议try中的代码尽可能少。避免catch住未预期的异常,掩藏掉真正的错误。

·[建议] 建议使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码,这对于清理资源常常很有用。例如:文件关闭。

解释

  • 用户经常需要以统一的方式处理某模块抛出的所有异常,如果模块开发者自定义的异常没有统一基类,用户处理起来就很麻烦
  • except:except Exception:except StandardError:会捕获类似语法错误、Ctrl+C中断等底层异常。而一般情况下这不是用户代码要处理的
  • 逗号式异常捕获语法将废弃

示例

# yes
try: raise Error except Error as error: pass

# no
try:
    raise Error
except Error, error:
    pass

# Yes
try:     
    value = collection[key]
except KeyError:     
    return key_not_found(key)
else:     
    return handle_value(value)
# No try: # 太泛了! return handle_value(collection[key]) except KeyError: # 会捕捉到handle_value()中的KeyError return key_not_found(key)

  

2.3. 全局变量

·[强制] 禁止使用全局变量。除了以下例外:
  • 脚本默认参数
  • 模块级常量

·[强制] 如果定义全局变量,必须写在文件头部。

解释

  • 全局变量破坏程序封装性,使代码维护困难

2.4. 构造函数

·[建议] 类构造函数应该尽量简单,不能包含可能失败或过于复杂的操作

解释

  • 复杂的、可能出异常的构造函数通常语义不明确,与调用者的期望不符。且难以维护,容易出错。一般建议把复杂部分拆分到独立的函数中。
  • 什么情况算”过于”复杂,由代码owner和reviewer根据经验自行判定

2.5. 函数返回值

·[强制] [PY004] 函数返回值必须小于等于3个。3个以上时必须通过class/namedtuple/dict等具名形式进行包装

解释

  • 虽然python支持以tuple的形式返回多个值,但返回值过多时需要用户记住返回值顺序,使接口难以使用,容易用错

示例

YES:

def get_numbers():
    return 1, 2, 3

a, b, c = get_numbers()

class Person(object):
    def __init__(self, name, gender, age, weight):
        self.name = name
        self.gender = gender
        self.age = age
        self.weight = weight

or
import collections
Person = collections.namedtuple('Person', 'name gender age weight')

def get_person_info():
    return Person('jjp', 'MALE', 30, 130)

person = get_person_info()
NO:

def get_numbers():
    return 1, 2, 3, 4

a, b, c, d = get_numbers()

def get_person_info():
    return 'jjp', 'MALE', 30, 130

name, gender, age, weight = get_person_info()

2.6. 嵌套/局部/内部类或函数

·[建议] 不推荐使用嵌套/局部/内部类或函数

解释

  • 这些机制增加了代码理解难度,考虑到公司的平均python水平,不推荐使用

2.7. 列表推导

·[强制] [PY011] 可以使用列表推导。mapping、loop、filter部分单独成行,且最多只能写一行。禁止多层loop或filter。

解释

  • 复杂的列表推导难以理解,建议转换成对应的for循环

示例

YES:

result = []
for x in range(10):
    for y in range(5):
        if x * y > 10:
            result.append((x, y))

for x in xrange(5):
    for y in xrange(5):
        if x != y:
            for z in xrange(5):
                if y != z:
                    yield (x, y, z)

return ((x, complicated_transform(x))
      for x in long_generator_function(parameter)
      if x is not None)

squares = [x * x for x in range(10)]

eat(jelly_bean for jelly_bean in jelly_beans
    if jelly_bean.color == 'black')
NO: result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] return ((x, y, z) for x in xrange(5) for y in xrange(5) if x != y for z in xrange(5) if y != z)

  

2.8. 默认迭代器和操作符

·[强制] 对容器或文件的只读遍历,应该使用内置的迭代方法,不要使用返回list的方式遍历。

·[强制] [PY013] 对容器类型,使用innot in判断元素是否存在。而不是has_key

解释

  • 可读性和性能更好。例如:
    • file.readlines()会一次性读入文件中所有行,存储到一个list中返回。当文件很大时,性能开销较大
  • 当文件或容器较小时,调用返回list的函数也可以接受。请reviewer视具体情况决定是否通过。

  • 如果遍历过程中需要对容器进行增删,请使用返回list的方式遍历

示例

Yes:

for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in dict.iteritems(): ...

NO:

for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
 

2.9. 生成器

·[建议] 当返回较长列表数据时建议使用yield和generator函数。

解释

  • 生成器函数可以避免返回大列表,占用过多内存、影响性能。同时还可以保持代码的优雅易读。

示例

YES:
#返回n以内的奇数
def odds(n):
    for i in xrange(1, n + 1):
        if i % 2 == 1:
            yield i

for i in odds(1000):
    print i
NO: #返回n以内的奇数 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

  

2.10. lambda函数 

·[强制] [PY014] 可以使用lambda函数,但仅限一行之内。

解释

  • lambda函数可以简化开发。但复杂的lambda函数可读性很差,应该避免

2.11. 条件表达式

·[强制] 条件表达式仅用于一行之内,禁止嵌套使用

解释

  • 语法略小众,其它语言背景开发者(如C++)看起来比较困惑。写复杂了难以阅读维护

示例

YES:

# 单行简单条件表达式
x = true_value if cond else false_value

# 改写成if形式
if cond:
    x = true_value
else
    x = false_value

# 复杂if表达式
if t < 60:
    unit = "seconds"
elif t < 3600:
    unit = "minutes"
else
    unit = "hours"
NO:

#嵌套条件表达式
unit = "seconds" if t < 60 else "minutes" if t < 3600 else "hours"

  

2.12. 默认参数

·[强制] [PY016] 仅可使用以下基本类型字面常量或常量作为默认参数:整数、bool、浮点、字符串、None

解释

  • 以可修改的对象(如list、dict、object等)作为默认参数,可能会被不小心改掉,导致默认值发生变化,产生难以追查的错误
  • 默认值在module加载时求值,而不是每次调用时求值。复杂的默认值可能和预期不符合(例如下边例子中的time.time()FLAGS.my_things )

示例

YES:

def foo(a, b=None):
    if b is None:
        b = []
No:  def foo(a, b=[]):
         ...
No:  def foo(a, b=time.time()):  # The time the module was loaded???
         ...
No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...
         ...

  

2.13. 属性(properties)

·[强制] 可以使用property。但禁止在派生类里改写property实现。

解释

  • 由于property是在基类中定义的,默认绑定到基类的实现函数。若允许在派生类中改写property实现,则需要在基类中通过间接方式调用property实现函数。这个方法技巧性太强,可读性差,所以禁止使用。

示例

YES:

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)

    area = property(__get_area, __set_area,
                    doc="""Gets or sets the area of the square.""")

    @property
    def perimeter(self):
        return self.side * 4
NO: class MySquare(Square): def __get_area(self): return math.pi * self.side ** 2 # overwrite doesn't work def __set_area(self, area): self.side = math.sqrt(area / math.pi) # overwrite doesn't work

  

2.14. True/False求值

·[建议] 建议显式转换到bool类型,慎用到bool类型的隐式转换。如使用隐式转换,你需要确保充分了解其语义

·[强制] [PY018] 禁止使用==!=判断表达式是否为None,应该用isis not None

·[强制] 当明确expr为bool类型时,禁止使用==!=True/False比较。应该替换为exprnot expr

·[强制] 判断某个整数表达式expr是否为零时,禁止使用not expr,应该使用expr == 0

解释

  • python中None、空字符串、0、空tuple、list、dict都会被隐式转换为False,这可能和用户预期的行为不一致。

  • 为便于不太熟悉python语言的其它语言开发者理解python代码,建议”显式”表明到bool类型的转换语义

  • 运算符==!=的结果取决于__eq__函数,可能出现obj is not None,但obj==None的情况

  • if expr != False 当expr为None时,会通过检测。一般这不是用户期望的行为
    • from PEP:
      • Yes: if greeting:
      • No: if greeting == True:
      • Worse: if greeting is True:
  • 当判断expr是否为0时,若expr为None,not expr也会返回True。一般这不是用户期望的行为

示例

YES:
if users is None or len(users) == 0:
    print 'no users'

if foo == 0:
    self.handle_zero()

if i % 10 == 0:
    self.handle_multiple_of_ten()
Cautious:

if not users:
    print "no users"
NO: if len(users) == 0: print 'no users' if foo is not None and not foo: self.handle_zero() if not i % 10: self.handle_multiple_of_ten()

  

2.15. 函数和方法装饰器

·[建议] 建议仅在以下几种情况下使用函数方法装饰器。其它情况下如有明显好处,且不影响代码可维护性,可以谨慎使用
  • @property@classmethod@staticmethod
  • 自动化测试
  • 第三方库要求使用的装饰器

解释

  • decorator太灵活,可以任意改变函数参数和返回值,容易产生非预期行为。滥用容易使代码可维护性性变差
  • decorator在import时执行,decorator代码执行中的错误很难处理和恢复

 

posted @ 2019-11-08 17:00  西二旗老实人  阅读(408)  评论(0编辑  收藏  举报