Оглавление

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   3 строки и код готов

Некоторое сокращение допустимо: предположим, вы хотите получить содержимое координаты:

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

и другой координаты:

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'])

Но в третьем приближении становится видно, что придётся писать этот код снова и снова. Пора заняться рефакторингом:

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 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 язык с динамической типизацией, и если вы не испытываете чёткой потребности в получении списка, то и не проверяйте, список ли это. Просто выполняйте свою работу (например, итерируйтесь по нему), а вызывающий может предоставить любой тип итерируемого или контейнер.

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]

кроме того, тип в Python — единичное множество, и можно просто проверить инентичность, что читается лучше:

# 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   Знайте про встроенное

Вы должны обладать хотя бы базовым пониманием встроенных функций Python (http://docs.python.org/library/functions.html)

Наример, isinstance может принимать более одного типа в качестве второго аргумента, так что можно написать:

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

А ещё dict.get, чей второй аргумент по-умолчанию None:

# very very redundant
value = my_dict.get('key', None)
# good
value= my_dict.get('key')

А ещё, if 'key' in my_dict и if my_dict.get('key') имеют разные значения. Проверьте, что правильно используете их.

1.16   Изучаем обозреватели списков

При правильном использовании, обозреватели списком могут значительно увеличить качество фрагмента кода при отображении или фильтрации коллекции:

# not very good
cube = []
for i in res:
        cube.append((i['id'],i['name']))
# better
cube = [(i['id'], i['name']) for i in res]

Однако будьте бдительны: с великой силой приходит и ответственность, и обозреватели списков могут стать достаточно сложными. В этом случае вернитесь к использованию нормальных циклов for, извлекайте функции и выполняйте преобразования в несколько шагов.

1.17   Изучите стандартную библиотеку

Python поставляется с "батарейками в комплекте", но эти батарейки обычно преступно недоиспользуются. Некоторые стандартные модули, с которыми стоит познакомиться — itertools, operator и collections, и прочие (хотя будьте внимательны к версии python):

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

Великолепными источниками по этой теме можно назвать официальную документацию stdlib (http://docs.python.org/library/index.html) и Модуль недели в Python (http://www.doughellmann.com/projects/PyMOTW/, подпишитесь на их RSS).

1.18   Коллекции — тоже логические величины

В Pythob многие объекты имеют логическое значение, когда проверяются в логическом контексте (например, с помощью if). Среди них — коллекции (списки, словари, наборы и т.д.), которые "ложны", когда пусты и "истинны" когда содержат элементы:

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

а следовательно не смысла вызывать 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 — ваш друг, если надо что-то проитерировать:

# 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 redendunt but sometime using such temporary variables helps debuging the code easier 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

Например, если вы хотите изменить контейнер:

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

Вы должны использовать константу, дать ей корректное имя и, возможно, комментарий, объясняющий происхождение такого значения. И конечно будет понятнее и легче читать такой код и будет только одно место, в котором надо будет произвести изменения. И это верно не только для цифр, но и для любого символьного значения, которое семантически представляет из себя константу.

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

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