1 Python代码风格指南

你可以在以下链接找到些有兴趣的信息作为本准则的增补

1.1 魔术方法

魔术方法(以两个下划线开头 结尾)*不* 能直接调用除非你用一个同样的名字的方法来覆盖掉它。

魔术方法是要么一般是用来实现某些协议而让你调用,要么是通过使用他们实现的某些操作符或某些操作:

  1. # bad
  2. levels.__contains__(name)
  3. # good
  4. name in levels
  5. # very bad
  6. kw.__setitem__('nodes',nodes)
  7. # much better
  8. kw['nodes'] = nodes

1.2 .clone()

很少需要(除非你真的不知道你所要克隆的变量是什么类型),尤其对于系统内置的集合类型是完全不需要的:你只需对你已存在的集合调用构造方法。

  1. # bad
  2. new_dict = my_dict.clone()
  3. # good
  4. new_dict = dict(my_dict)
  5. # bad
  6. new_list = old_list.clone()
  7. # good
  8. new_list = list(old_list)

更不要手工克隆

  1. # surely you jest!
  2. values = []
  3. for val in view_items:
  4. values += [val]
  5. # sane
  6. values = list(view_items)

1.3 “clone 和 update”

dict 构造函数需要(可选)一个有位置的参数(或者是字典类的对象或者是二维元组的迭代器)和无限数量的关键字参数。因此,你可以“合并”两个不同的字典成为新的字典:

  1. # bad
  2. dictionary3 = dictionary1.clone()
  3. dictionary3.update(dictionary2)
  4. # worse
  5. dictionary3 = {}
  6. dictionary3.update(d1)
  7. dictionary3.update(d2)
  8. # good
  9. dictionary3 = dict(dictionary1, **dictionary2)

你可以通过这些进行简单操作,例如克隆一个字典而且替换掉某些键值:

  1. # no
  2. context = kw.clone()
  3. context['foo'] = 'bar'
  4. # yes
  5. context = dict(kw, foo='bar')

1.4 “手动 update”

dict.update 用法像 dict() :一个可选的位置参数和无限数量的键值参数。

因此以下用法都是可以的

从一个字典合并另外一个:

  1. # bad
  2. for key, value in other_dict.iteritems():
  3. my_dict[key] = value
  4. # good
  5. my_dict.update(other_dict)

批量设置键值

  1. # bad
  2. my_dict['foo'] = 3
  3. my_dict['bar'] = 4
  4. my_dict['baz'] = 5
  5. # good
  6. my_dict.update(
  7. foo=3,
  8. bar=4,
  9. baz=5)

1.5 Java 的字典创建方式

Python 不是 java,他有字面值

  1. # very very very bad
  2. my_dict = {}
  3. my_dict['foo'] = 3
  4. my_dict['bar'] = 4
  5. my_dict['baz'] = 5
  6. # good
  7. my_dict = {
  8. 'foo': 3,
  9. 'bar': 4,
  10. 'baz': 5
  11. }

1.6 “临时的 kwargs”

关键字参数是获取不定参数的一种好方法。你只需要这样使用他们:

  1. def foo(**kwargs):
  2. logging.debug('Calling foo with arguments %s', kwargs)
  3. return bar(**kwargs)

或者如果你获取一个现成的字典(从另一个函数),并希望它的内容传递给另一个函数或方法:

  1. sessions = some_function_returning_a_dict_of_sessions()
  2. some_other_function(**sessions)

不过无需要创建一个专门的字典来传递``**kwargs``,只需要提供那该死的关键字参数:

  1. # waste of time and space
  2. my_dict = {
  3. 'foo': 3,
  4. 'bar': 4,
  5. 'baz': 5
  6. }
  7. some_func(**my_dict)
  8. # good
  9. some_func(foo=3, bar=4, baz=5)

1.7 (正式和非正式)过时的方法

dict.has_key(key) 已经过时,请使用 in 操作符:

  1. # bad
  2. kw.has_key('cross_on_pages')
  3. # good
  4. 'cross_on_pages' in kw

1.8 没必要的中间变量

通过命名临时变量可以使代码更加清晰,但并不是说你所有情况都要建临时变量

  1. # pointless
  2. schema = kw['schema']
  3. params = {'schema': schema}
  4. # simpler
  5. params = {'schema': kw['schema']}

1.9 让冗余代码三振出局

一点的冗余是可以接受的:可能你需要获取axis的内容

  1. col_axes = []
  2. if 'col_axis' in kw:
  3. col_axes = self.axes(kw['col_axis'])

and a second one:

  1. col_axes = []
  2. if 'col_axis' in kw:
  3. col_axes = self.axes(kw['col_axis'])
  4. page_axes= []
  5. if 'page_axis' in kw:
  6. page_axes = self.axes(kw['page_axis'])

但是第三次,你需要一次又一次修改。是时候重构:

  1. def get_axis(self, name, kw):
  2. if name not in kw:
  3. return []
  4. return self.axes(kw[name])
  5. #[…]
  6. col_axes = self.get_axis('col_axis', kw)
  7. page_axes = self.get_axis('page_axis', kw)

也可以对已调用的方法进行重构(为了不破坏其他代码或者测试,注意检查方法哪里被调用):

  1. # from
  2. def axes(self, axis):
  3. axes = []
  4. if type(axis) == type([]):
  5. axes.extend(axis)
  6. else:
  7. axes.append(axis)
  8. return axes
  9. def output(self, **kw):
  10. col_axes = []
  11. if 'col_axis' in kw:
  12. col_axes = self.axes(kw['col_axis'])
  13. page_axes = []
  14. if 'page_axis' in kw:
  15. page_axes = self.axes(kw['page_axis'])
  16. cross_on_rows = []
  17. if 'cross_on_rows' in kw:
  18. cross_on_rows = self.axes(kw['cross_on_rows'])
  19. # to:
  20. def axes(self, axis):
  21. if axis is None: return []
  22. axes = []
  23. if type(axis) == type([]):
  24. axes.extend(axis)
  25. else:
  26. axes.append(axis)
  27. return axes
  28. def output(self, **kw):
  29. col_axes = self.axes(kw.get('col_axis'))
  30. page_axes = self.axes(kw.get('page_axis'))
  31. cross_on_rows = self.axes(kw.get('cross_on_rows'))

1.10 简单的多个返回值是可以接受的

  1. # a bit complex and with a redundant temp variable
  2. def axes(self, axis):
  3. axes = []
  4. if type(axis) == type([]):
  5. axes.extend(axis)
  6. else:
  7. axes.append(axis)
  8. return axes
  9. # clearer
  10. def axes(self, axis):
  11. if type(axis) == type([]):
  12. return list(axis) # clone the axis
  13. else:
  14. return [axis] # single-element list

1.11 尽量避免类型测试

Python是一种动态类型语言,如果你并不是必定要接受一个列表,那就不要测试它是不是列表,而是继续你的操作(例如:迭代它,然后调用他的时候可以提供任何类型的迭代器或者容器)

1.12 除非你知道要的是什么类型,否则不要使用 type

列表的类型是 list ,字典的类型是 dict:

  1. # bad
  2. def axes(self, axis):
  3. if type(axis) == type([]): # we already know what the type of [] is
  4. return list(axis)
  5. else:
  6. return [axis]
  7. # good
  8. def axes(self, axis):
  9. if type(axis) == list:
  10. return list(axis)
  11. else:
  12. return [axis]

plus Python types are singletons, so you can just test for identity, it reads better:

  1. # better
  2. def axes(self, axis):
  3. if type(axis) is list:
  4. return list(axis)
  5. else:
  6. return [axis]

1.13 如果真的想用,那就用python提供的类型测试

在上面的代码如果提供的是 list 的子类则会返回失败(可能我们需要的是允许),因为 \== 和 is 不会检查子类型,但是``isinstance`` 可以:

  1. # shiny
  2. def axes(self, axis):
  3. if isinstance(axis, list):
  4. return list(axis) # clone the axis
  5. else:
  6. return [axis] # single-element list

1.14 不要只是为了调用函数而创造新函数

  1. # dumb, ``str`` is already callable
  2. parent_id = map(lambda x: str(x), parent_id.split(','))
  3. # better
  4. parent_id = map(str, parent_id.split(','))

1.15 了解内建函数

You should at least have a basic understanding of all the Python builtins (http://docs.python.org/library/functions.html)

For example, isinstance can take more than one type as its second argument, so you could write:

  1. def axes(self, axis):
  2. if isinstance(axis, (list, set, dict)):
  3. return list(axis)
  4. else:
  5. return [axis]

Another one is dict.get, whose second argument defaults to None:

  1. # very very redundant
  2. value = my_dict.get('key', None)
  3. # good
  4. value= my_dict.get('key')

Also, if ‘key’ in my_dict and if my_dict.get(‘key’) have very different meaning, be sure that you’re using the right one.

1.16 学习列表推导式

When used correctly, list comprehensions can greatly enhance the quality of a piece of code when mapping and/or filtering collections:

  1. # not very good
  2. cube = []
  3. for i in res:
  4. cube.append((i['id'],i['name']))
  5. # better
  6. cube = [(i['id'], i['name']) for i in res]

But beware: with great power comes great responsibility, and list comprehensions can become overly complex. In that case, either revert back to “normal” for loops, extract functions or do your transformation in multiple steps

1.17 学习你的标准库

Python is provided “with batteries included”, but these batteries are often criminally underused. Some standard modules to know are itertools, operator and collections, among others (though be careful to note the python version at which functions and objects were introduced, don’t break compatibility with the officially supported versions for your tool):

  1. # no
  2. cube = map(lambda i: (i['id'], i['name']), res)
  3. # still no
  4. cube = [(i['id'], i['name']) for i in res]
  5. # yes, with operator.itemgetter
  6. cube = map(itemgetter('id', 'name'), res)

Excellent resources for this are the official stdlib documentation (http://docs.python.org/library/index.html ) and Python Module of the Week (http://www.doughellmann.com/projects/PyMOTW/, do subscribe to its RSS feed)

1.18 Collections 也是布尔类型

In python, many objects have “boolean-ish” value when evaluated in a boolean context (such as an if). Among these are collections (lists, dicts, sets, …) which are “falsy” when empty and “truthy” when containing items:

  1. bool([]) is False
  2. bool([1]) is True
  3. bool([False]) is True

therefore, no need to call len:

  1. # redundant
  2. if len(some_collection):
  3. "do something..."
  4. # simpler
  5. if some_collection:
  6. "do something..."

1.19 你可以把一个对象添加到列表中

  1. # no
  2. some_list += [some_item]
  3. # yes
  4. some_list.append(some_item)
  5. # very no
  6. view += [(code, values)]
  7. # yes
  8. view.append((code, values))

1.20 列表相加

  1. # obscure
  2. my_list = []
  3. my_list += list1
  4. my_list += list2
  5. # simpler
  6. my_list = list1 + list2

1.21 学习你的标准库 (2)

Itertools is your friend for all things iterable:

  1. # ugly
  2. my_list = []
  3. my_list += list1
  4. my_list += list2
  5. for element in my_list:
  6. "do something..."
  7. # unclear, creates a pointless temporary list
  8. for element in list1 + list2:
  9. "do something..."
  10. # says what I mean
  11. for element in itertools.chain(list1, list2):
  12. "do something..."

1.22 遍历可迭代对象

  1. # creates a temporary list and looks bar
  2. for key in my_dict.keys():
  3. "do something..."
  4. # better
  5. for key in my_dict:
  6. "do something..."
  7. # creates a temporary list
  8. for key, value in my_dict.items():
  9. "do something..."
  10. # only iterates
  11. for key, value in my_dict.iteritems():
  12. "do something..."

1.23 链接调用是可以的,只要你不要滥用

  1. # what's the point of the ``graph`` temporary variable?
  2. # plus it shadows the ``graph`` module, bad move
  3. graph = graph.Graph(kw)
  4. mdx = ustr(graph.display())
  5. # just as readable
  6. mdx = ustr(grah.Graph(kw).display())

NOTE:

yes, here the temporary variable graph is redundant but sometimes using such temporary variables simplify code debugging when you want to inspect the variable and you put breakpoint on the single line expression it’s difficult to know when to do step-in and step-out.

1.24 使用 dict.setdefault

If you need to modify a nested container for example:

  1. # longer.. harder to read
  2. values = {}
  3. for element in iterable:
  4. if element not in values:
  5. values[element] = []
  6. values[element].append(other_value)
  7. # better.. use dict.setdefault method
  8. values = {}
  9. for element in iterable:
  10. values.setdefault(element, []).append(other_value)

1.25 使用默认值,远离“神奇数字”

  1. # bad
  2. limit = 20
  3. # bad
  4. search(cr, uid, ids, domain, limit=20, context=context)

You should use a constant, name it correctly, and perhaps add a comment on it explaining where the value comes from. And of course it’s cleaner, easier to read and there’s only one place to modify. Oh and that is true not just for numbers, but for any literal value that is semantically a constant!

  1. # better
  2. DEFAULT_SEARCH_LIMIT = 20 # limit to 20 results due to screen size
  3. search(cr, uid, ids, domain, limit=DEFAULT_LIMIT, context=context)