1   Python代码风格指南

In additions to these guidelines, you may also find the following link interesting:

1.1   魔术方法

magic methods (starting and ending with two underscores) should not have to be called directly unless you’re overriding a method of the same name.

magic methods are used to implement specific protocols and are called for you, either due to operator access or due to some special operation using them:

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

1.2   .clone()

rarely necessary (unless you really have no idea what the type of the variable you’re trying to clone is), never necessary for built-in collections: just call the constructor with your existing collection:

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

And don’t clone manually, please:

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

1.3   “clone 和 update”

the dict constructor takes both a single (optional) positional argument (either a dictionary-like object or an iterable of 2-tuples) and an unlimited number of keyword arguments. Thus, you can “merge” two different dictionaries into a third, new, dictionary:

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

You can use those properties for simpler operations, such as cloning an existing dict and (re) setting a key:

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

1.4   “手动 update”

the signature of dict.update is the same as dict(): a single, optional, positional argument and an unlimited number of keyword arguments.

Thus the following are possible:

merging a dict from another one:

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

Setting a bunch of keys at the same time:

# 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 isn’t java, it has literals:

# 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”

keyword arguments are a good way to get a bunch of unspecified supplementary arguments if e.g. you just want to forward them:

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

or if you retrieved a ready-made dict (from another function) and want to pass its content to another function or method:

sessions = some_function_returning_a_dict_of_sessions()
some_other_function(**sessions)

but there is no point whatsoever in creating a dict for the sake of passing it as **kwargs, just provide the damn keyword arguments:

# 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) is deprecated, please use the in operator:

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

1.8   没必要的中间变量

Temporary variables can make the code clearer by giving names to objects, but that doesn’t mean you should create temporary variables all the time:

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

1.9   让冗余代码三振出局

A bit of redundancy can be accepted: maybe you have to get the content of an axis:

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

and a second one:

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

But at the third strike, chances are you’re going to need the thing again and again. Time to refactor:

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)

The refactoring could also be an improvement of a method already called (be sure to check where the method is called in order not to break other pieces of code. Or write tests):

# 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 kw.has_key('col_axis'):
                col_axes = self.axes(kw['col_axis'])
        page_axes = []
        if kw.has_key('page_axis'):
                page_axes = self.axes(kw['page_axis'])
        cross_on_rows = []
        if kw.has_key('cross_on_rows'):
                 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 is a dynamically typed languages, if you don’t absolutely need to receive a list, then don’t test for a list, just do your stuff (e.g. iterating on it, then caller can provide any kind of iterator or container)

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

The type of a list is list, the type of a dict is 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提供的类型测试

The previous piece of code will fail if the caller provided a subclass of list (which is possible and allowed), because == and is don’t check for subtypes. isinstance does:

# 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)