Tip
对你的代码运行pychecker
确保对你的代码运行pychecker.
关于如何运行pychecker的更多信息, 参考 pychecker主页
你可以设置一个叫做__pychecker__的模块级变量来抑制适当的告警. 例如:
__pychecker__ = 'no-callinit no-classattr'
采用这种抑制方式的好处是我们可以轻松查找抑制并回顾它们.
你可以使用 pychecker --help 来获取pychecker告警列表.
要抑制”参数未使用”告警, 你可以用”_”作为参数标识符, 或者在参数名前加”unused_”. 遇到不能改变参数名的情况, 你可以通过在函数开头”提到”它们来消除告警. 例如:
def foo(a, unused_b, unused_c, d=None, e=None):
(d, e) = (d, e) # 让pychecker不告警
return a
理想情况下, 我们以后会扩展pychecker以确保你真的没有使用这些参数.
Tip
仅对包和模块使用导入
使用 import x 来导入包和模块.
使用 form x import y , 其中x是包前缀, y是不带前缀的模块名.
使用 form x import y as z, 如果两个要导入的模块都叫做z或者y太长了.
例如, 模块 sound.effects.echo 可以用如下方式导入:
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)
导入时不要使用相对名称. 即使模块在同一个包中, 也要使用完整包名. 这能帮助你避免无意间导入一个包两次.
Tip
使用模块的全路径名来导入每个模块
所有的新代码都应该用完整包名来导入每个模块.
应该像下面这样导入:
# Reference in code with complete name.
import sound.effects.echo
# Reference in code with just module name (preferred).
from sound.effects import echo
Tip
允许使用异常, 但必须小心
异常必须遵守特定条件:
像这样触发异常: raiseMyException("Error message") 或者raise MyException . 不要使用两个参数的形式(raise MyException,"Error message" )或者过时的字符串异常(raise "Errormessage" ).
模块或包应该定义自己的特定域的异常基类, 这个基类应该从内建的Exception类继承. 模块的异常基类应该叫做”Error”.
class Error(Exception): pass
永远不要使用 except: 语句来捕获所有异常, 也不要捕获Exception 或者 StandardError , 除非你打算重新触发该异常, 或者你已经在当前线程的最外层(记得还是要打印一条错误消息). 在异常这方面, Python非常宽容,except: 真的会捕获包括Python语法错误在内的任何错误. 使用except: 很容易隐藏真正的bug.
尽量减少try/except块中的代码量. try块的体积越大, 期望之外的异常就越容易被触发. 这种情况下, try/except块将隐藏真正的错误.
使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码. 这对于清理资源常常很有用, 例如关闭文件.
Tip
避免全局变量
避免使用全局变量, 用类变量来代替. 但也有一些例外:
Tip
鼓励使用嵌套/本地/内部类或函数
Tip
可以在简单情况下使用
适用于简单情况. 每个部分应该单独置于一行: 映射表达式, for语句, 过滤器表达式. 禁止多重for语句或过滤器表达式. 复杂情况下还是使用循环.
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)
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')
Tip
如果类型支持, 就使用默认迭代器和操作符. 比如列表, 字典及文件等.
如果类型支持, 就使用默认迭代器和操作符, 例如列表, 字典和文件. 内建类型也定义了迭代器方法. 优先考虑这些方法, 而不是那些返回列表的方法. 除非你在遍历容器时不能修改它.
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(): ...
Tip
按需使用生成器.
Tip
适用于单行函数
Tip
适用于大部分情况.
鼓励使用, 不过有如下注意事项:
不要在函数或方法定义中使用可变对象作为默认值.
Yes: def foo(a, b=None):
if b is None:
b = []
No: def foo(a, b=[]):
...
调用方代码必须为带有默认值的参数使用带有名字的值. 这多少能增加代码的可读性, 并且当增加参数时能避免和检测接口被破坏.
def foo(a, b=1):
...
Yes: foo(1)
foo(1, b=2)
No: foo(1, 2)
Tip
访问和设置数据成员时, 你通常会使用简单, 轻量级的访问和设置函数. 建议用属性来代替它们.
你通常习惯于使用访问或设置方法来访问或设置数据, 它们简单而轻量. 不过我们建议你在新的代码中使用属性. 只读属性应该用 @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 ___get_area(self):
"""Indirect accessor for 'area' property."""
return self.__get_area()
def __set_area(self, area):
"""Sets the 'area' property."""
self.side = math.sqrt(area)
def ___set_area(self, area):
"""Indirect setter for 'area' property."""
self._SetArea(area)
area = property(___get_area, ___set_area,
doc="""Gets or sets the area of the square.""")
@property
def perimeter(self):
return self.side * 4
(译者注: 老实说, 我觉得这段示例代码很不恰当, 有必要这么蛋疼吗?)
Tip
尽可能使用隐式false
尽可能使用隐式的false, 例如: 使用 iffoo: 而不是 iffoo != []: . 不过还是有一些注意事项需要你铭记在心:
永远不要用==或者!=来比较单件, 比如None. 使用is或者is not.
注意: 当你写下 ifx: 时, 你其实表示的是 ifx is notNone . 例如: 当你要测试一个默认值是None的变量或参数是否被设为其它值. 这个值在布尔语义下可能是false!
永远不要用==将一个布尔量与false相比较. 使用 ifnot x: 代替. 如果你需要区分false和None, 你应该用像if notx and xis not None: 这样的语句.
对于序列(字符串, 列表, 元组), 要注意空序列是false. 因此 if not seq: 或者if seq: 比if len(seq): 或if notlen(seq): 要更好.
处理整数时, 使用隐式false可能会得不偿失(即不小心将None当做0来处理). 你可以将一个已知是整型(且不是len()的返回结果)的值与0比较.
Yes: if not users: print 'no users' if foo == 0: self.handle_zero() if i % 10 == 0: self.handle_multiple_of_ten()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()
注意‘0’(字符串)会被当做true.
Tip
尽可能使用字符串方法取代字符串模块. 使用函数调用语法取代apply(). 使用列表推导, for循环取代filter(), map()以及reduce().
我们不使用不支持这些特性的Python版本, 所以没理由不用新的方式.
No: words = string.split(foo, ':')
map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))
apply(fn, args, kwargs)
Yes: words = foo.split(':')
[x[1] for x in my_list if x[2] == 5]
fn(*args, **kwargs)
Tip
推荐使用
嵌套的Python函数可以引用外层函数中定义的变量, 但是不能够对它们赋值. 变量绑定的解析是使用静态Scoping, 也就是基于静态的程序文本. 对一个块中的某个名称的任何赋值都会导致Python将对该名称的全部引用当做局部变量, 甚至是赋值前的处理. 如果碰到global声明, 该名称就会被视作全局变量.
一个使用这个特性的例子:
def get_adder(summand1):
"""Returns a function that adds numbers to a given number."""
def adder(summand2):
return summand1 + summand2
return adder
(译者注: 这个例子有点诡异, 你应该这样使用这个函数: sum= get_adder(summand1)(summand2) )
可能导致让人迷惑的bug. 例如下面这个例子:
i = 4
def foo(x):
def bar():
print i,
# ...
# A bunch of code here
# ...
for i in x: # Ah, i *is* local to Foo, so this is what Bar sees
print i,
bar()
因此 foo([1, 2, 3]) 会打印 12 3 3 , 不是1 23 4 .
(译者注: x是一个列表, for循环其实是将x中的值依次赋给i.这样对i的赋值就隐式的发生了, 整个foo函数体中的i都会被当做局部变量, 包括bar()中的那个. 这一点与C++之类的静态语言还是有很大差别的.)
Tip
如果好处很显然, 就明智而谨慎的使用装饰器
用于函数及方法的装饰器(也就是@标记). 最常见的装饰器是@classmethod 和@staticmethod, 用于将常规函数转换成类方法或静态方法. 不过, 装饰器语法也允许用户自定义装饰器. 特别地, 对于某个函数my_decorator , 下面的两段代码是等效的:
class C(object):
@my_decorator
def method(self):
# method body ...
class C(object):
def method(self):
# method body ...
method = my_decorator(method)
如果好处很显然, 就明智而谨慎的使用装饰器. 装饰器应该遵守和函数一样的导入和命名规则. 装饰器的python文档应该清晰的说明该函数是一个装饰器. 请为装饰器编写单元测试.
避免装饰器自身对外界的依赖(即不要依赖于文件, socket, 数据库连接等), 因为装饰器运行时这些资源可能不可用(例如导入时, 使用pychecker或其它工具时). 应该保证一个用有效参数调用的装饰器在所有情况下都是成功的.
装饰器是一种特殊形式的”顶级代码”. 参考后面关于Main的话题.
Tip
不要依赖内建类型的原子性.
虽然Python的内建类型例如字典看上去拥有原子操作, 但是在某些情形下它们仍然不是原子的(即: 如果__hash__或__eq__被实现为Python方法)且它们的原子性是靠不住的. 你也不能指望原子变量赋值(因为这个反过来依赖字典).
优先使用Queue模块的 Queue 数据类型作为线程间的数据通信方式. 另外, 使用threading模块及其锁原语. 了解条件变量的合适使用方式, 这样你就可以使用threading.Condition 来取代低级别的锁了.
Tip
避免使用这些特性