Versions

This is the documentation for older versions of Odoo (formerly OpenERP).

See the new Odoo user documentation.

See the new Odoo technical documentation.

1   Python代码风格指南

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

1.1   魔术方法

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

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

# bad
levels.__contains__(name)
# good
name in levels
# very bad
kw.__setitem__('nodes',nodes)
# much better
kw['nodes'] = nodes

1.2   .clone()

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

# bad
new_dict = my_dict.clone()
# good
new_dict = dict(my_dict)
# bad
new_list = old_list.clone()
# good
new_list = list(old_list)

更不要手工克隆

# surely you jest!
values = []
for val in view_items:
        values += [val]
# sane
values = list(view_items)

1.3   "clone 和 update"

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

# bad
dictionary3 = dictionary1.clone()
dictionary3.update(dictionary2)
# worse
dictionary3 = {}
dictionary3.update(d1)
dictionary3.update(d2)
# good
dictionary3 = dict(dictionary1, **dictionary2)

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

# no
context = kw.clone()
context['foo'] = 'bar'
# yes
context = dict(kw, foo='bar')

1.4   "手动 update"

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

因此以下用法都是可以的

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

# bad
for key, value in other_dict.iteritems():
        my_dict[key] = value
# good
my_dict.update(other_dict)

批量设置键值

# bad
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict.update(
        foo=3,
        bar=4,
        baz=5)

1.5   Java 的字典创建方式

Python 不是 java,他有字面值

# very very very bad
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict = {
        'foo': 3,
        'bar': 4,
        'baz': 5
}

1.6   "临时的 kwargs"

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

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

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

sessions = some_function_returning_a_dict_of_sessions()
some_other_function(**sessions)

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

# waste of time and space
my_dict = {
        'foo': 3,
        'bar': 4,
        'baz': 5
}
some_func(**my_dict)
# good
some_func(foo=3, bar=4, baz=5)

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

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

# bad
kw.has_key('cross_on_pages')
# good
'cross_on_pages' in kw

1.8   没必要的中间变量

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

# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}

1.9   让冗余代码三振出局

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

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

and a second one:

col_axes = []
if 'col_axis' in kw:
    col_axes = self.axes(kw['col_axis'])
page_axes= []
if 'page_axis' in kw:
    page_axes = self.axes(kw['page_axis'])

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

def get_axis(self, name, kw):
        if name not in kw:
            return []
        return self.axes(kw[name])
#[…]
col_axes = self.get_axis('col_axis', kw)
page_axes = self.get_axis('page_axis', kw)

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

# from
def axes(self, axis):
        axes = []
        if type(axis) == type([]):
                axes.extend(axis)
        else:
                axes.append(axis)
        return axes

def output(self, **kw):
        col_axes = []
        if 'col_axis' in kw:
                col_axes = self.axes(kw['col_axis'])
        page_axes = []
        if 'page_axis' in kw:
                page_axes = self.axes(kw['page_axis'])
        cross_on_rows = []
        if 'cross_on_rows' in kw:
                 cross_on_rows = self.axes(kw['cross_on_rows'])

# to:
def axes(self, axis):
        if axis is None: return []
        axes = []
        if type(axis) == type([]):
                axes.extend(axis)
        else:
                axes.append(axis)
        return axes

def output(self, **kw):
        col_axes = self.axes(kw.get('col_axis'))
        page_axes = self.axes(kw.get('page_axis'))
        cross_on_rows = self.axes(kw.get('cross_on_rows'))

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

# a bit complex and with a redundant temp variable
def axes(self, axis):
        axes = []
        if type(axis) == type([]):
                axes.extend(axis)
        else:
                axes.append(axis)
        return axes

 # clearer
def axes(self, axis):
        if type(axis) == type([]):
                return list(axis) # clone the axis
        else:
                return [axis] # single-element list

1.11   尽量避免类型测试

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

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

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

# bad
def axes(self, axis):
        if type(axis) == type([]): # we already know what the type of [] is
                return list(axis)
        else:
                return [axis]
# good
def axes(self, axis):
        if type(axis) == list:
                return list(axis)
        else:
                return [axis]

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

# better
def axes(self, axis):
        if type(axis) is list:
                return list(axis)
        else:
                return [axis]

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

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

# shiny
def axes(self, axis):
        if isinstance(axis, list):
                return list(axis) # clone the axis
        else:
                return [axis] # single-element list

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

# dumb, ``str`` is already callable
parent_id = map(lambda x: str(x), parent_id.split(','))
# better
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:

def axes(self, axis):
        if isinstance(axis, (list, set, dict)):
                return list(axis)
        else:
                return [axis]

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

# very very redundant
value = my_dict.get('key', None)
# good
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:

# not very good
cube = []
for i in res:
        cube.append((i['id'],i['name']))
# better
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):

# no
cube = map(lambda i: (i['id'], i['name']), res)
# still no
cube = [(i['id'], i['name']) for i in res]
# yes, with operator.itemgetter
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:

bool([]) is False
bool([1]) is True
bool([False]) is True

therefore, no need to call len:

# redundant
if len(some_collection):
        "do something..."
# simpler
if some_collection:
        "do something..."

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

# no
some_list += [some_item]
# yes
some_list.append(some_item)
# very no
view += [(code, values)]
# yes
view.append((code, values))

1.20   列表相加

# obscure
my_list = []
my_list += list1
my_list += list2
# simpler
my_list = list1 + list2

1.21   学习你的标准库 (2)

Itertools is your friend for all things iterable:

# ugly
my_list = []
my_list += list1
my_list += list2
for element in my_list:
        "do something..."
# unclear, creates a pointless temporary list
for element in list1 + list2:
        "do something..."
# says what I mean
for element in itertools.chain(list1, list2):
        "do something..."

1.22   遍历可迭代对象

# creates a temporary list and looks bar
for key in my_dict.keys():
        "do something..."
# better
for key in my_dict:
        "do something..."
# creates a temporary list
for key, value in my_dict.items():
        "do something..."
# only iterates
for key, value in my_dict.iteritems():
        "do something..."

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

# what's the point of the ``graph`` temporary variable?
# plus it shadows the ``graph`` module, bad move
graph = graph.Graph(kw)
mdx = ustr(graph.display())
# just as readable
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:

# longer.. harder to read
values = {}
for element in iterable:
    if element not in values:
        values[element] = []
    values[element].append(other_value)

# better.. use dict.setdefault method
values = {}
for element in iterable:
    values.setdefault(element, []).append(other_value)

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

# bad
limit = 20

# bad
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!

# better
DEFAULT_SEARCH_LIMIT = 20  # limit to 20 results due to screen size

search(cr, uid, ids, domain, limit=DEFAULT_LIMIT, context=context)