/pydonts

Рекомендации для студентов курса Python в Computer Science Center

pydonts

Общие рекомендации

  • Следуй PEP-8. Для автоматической проверки и исправления некоторых ошибок можно использовать утилиты pep8 и autopep8. К сожалению, они проверяют не всё (например, конвенции именования не проверяются). В качестве альтернативы можно использовать статический анализатор flake8, который помимо стиля проверит наличие некоторых простых ошибок. Плагин для него, проверяющий именование, называется pep8-naming.

  • Располагай импортируемые модули в начале файла.

  • Располагай импортируемые модули в лексикографическом порядке.

    # Плохо
    import gzip
    import sys
    from collections import defaultdict
    import io
    from contextlib import contextmanager
    import functools
    from urllib.request import urlopen
    
    # Лучше
    import functools
    import gzip
    import io
    import sys
    from collections import defaultdict
    from contextlib import contextmanager
    from urllib.request import urlopen
  • Используй операторы is и is not только для сравнение с синглтонами, например, None. Исключение: булевые значения True и False.

  • Помни и применяй falsy/truthy семантику. Falsy значения --- None, False, нули 0, 0.0, 0j, пустые строки и байты и все пустые коллекции. Все остальные значения truthy.

    # Плохо
    if acc == []:
        # ...
    
    # Плохо
    if len(acc) > 0:
        # ...
    
    # Лучше
    if not acc:
        # ...
    
    # Допустимо
    if acc == 0:
        # ...
  • Не называй переменные именами коллекций. Почти всегда можно подобрать более уместное имя.

  • Не копируй без необходимости.

    # Плохо
    set([x**2 for x in range(42)])
    
    for x in list(sorted(xs)):
        # ...
    
    # Лучше
    {x**2 for x in range(42)}
    
    for x in sorted(xs):
        # ...
  • Не используй dict.get и коллекцию dict.keys для проверки наличия ключа в словаре:

    # Плохо
    if key in g.keys():
        # ...
    
    if not g.get(key, False):
        # ...
    
    # Лучше
    if key in g:
        # ...
    
    if key not in g:
        # ...
  • Используй литералы для создания пустых коллекций. Исключение: set, литералов пустого множества в Python нет.

    # Плохо
    dict(), list(), tuple()
    
    # Лучше
    {}, [], ()

Структура кода

  • Не эмулируй оператор for, Python --- это не Scala.

    # Плохо
    i = 0
    while i < n:
        ...
        i += 1
    
    # Лучше
    for i in range(n):
        ...
  • Предпочитай итерацию по объекту циклам со счётчиком. Ошибка на 1 в индексе --- это классика. Если же индекс требуется, помни про enumerate.

    # Плохо
    for i in range(len(xs)) :
        x = xs[i]
    
    # Лучше
    for x in xs:
        ...
    
    # Или
    for i, x in enumerate(xs):
        ...
    # Плохо
    for i in range(min(len(xs), len(ys))):
        f(xs[i], ys[i])
    
    # Лучше
    for x, y in zip(xs, ys):
        f(x, y)
  • Не используй dict.keys для итерации по словарю.

    # Плохо
    for key in dict.keys():
        ...
    
    # Лучше
    for key in dict:
        ...
  • Не используй методы file.readline и file.readlines для итерации по файлу.

    # Плохо
    while True:
        line = file.readline()
        ...
    
    for line in file.readlines():
        ...
    
    
    # Лучше
    for line in file:
        ...
  • Не пиши бессмысленных операторов if и тернарных операторов.

    # Плохо
    if condition:
       return True
    else
       return False
    
    # Лучше
    return condition
    # Плохо
    if condition:
       return False
    else
       return True
    
    # Лучше
    return not condition
    # Плохо
    xs = [x for x in xs if predicate]
    return True if xs else False
    
    # Лучше
    xs = [x for x in xs if predicate]
    return bool(xs)
    
    # Ещё лучше
    return any(map(predicate, xs))

Функции

  • Избегай изменяемых значений по умолчанию.

  • Не злоупотребляй функциональными идиомами. Часто генератор списка, множества или словаря понятнее комбинации функций map, filter и zip.

    # Плохо
    list(map(lambda x: x ** 2,
             filter(lambda x: x % 2 == 1,
                    range(10))))
    
    # Лучше
    [x ** 2 for x in range(10) if x % 2 == 1]
  • Не злоупотребляй генераторами коллекций. Часто обычный цикл for понятней вложенного генератора.

  • Не сворачивай функции с эффектами. Первый аргумент functools.reduce не должен изменять состояние имён во внешних областях видимости или значение аккумулятора.

    # Плохо
    funtools.reduce(lambda acc, s: acc.update(s), sets,
    
    # Лучше
    acc = set()
    for set in sets:
        acc.update(set)
  • Избегай бессмысленных анонимных функций.

    # Плохо
    map(lambda x: frobnicate(x), xs)
    
    # Лучше
    map(frobnicate, xs)
    # Плохо
    collections.defaultdict(lambda: [])
    
    # Лучше
    collections.defaultdict(list)

Декораторы

  • Всегда используй functools.wraps или functools.update_wrapper при написании декоратора.

Строки

  • Используй методы str.startswith и str.endswith.

    # Плохо
    s[:len(p)] == p
    s.find(p) == len(s) - len(p)
    
    # Лучше
    s.startswith(p)
    s.endswith(p)
  • Используй форматирование строк вместо явных вызовов str и конкатенации.

    # Плохо
    "(+ " + str(expr1) + " " + str(expr2) + ")"
    
    # Лучше
    "(+ {} {})".format(expr1, expr2)

    Исключение: приведение к строке одного аргумента.

    # Плохо
    "{}".format(value)
    
    # Лучше
    str(value)
  • Не усложняй шаблон форматирования без необходимости.

    # Плохо
    "(+ {0} {1})"
    "(+ {expr1} {expr2})"
    
    # Лучше
    "(+ {} {})"
  • Помни, что метод str.format преобразует аргументы в строку.

    # Плохо
    "(+ {} {})".format(str(expr1), str(expr2))
    
    # Лучше
    "(+ {} {})".format(expr1, expr2)

Классы

  • Используй collections.namedtuple для классов с фиксированным набором неизменяемых полей.

    # Плохо
    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    # Лучше
    Point = namedtuple("Point", ["x", "y"])
  • Не вызывай "магические методы" напрямую, если для них есть функция или оператор.

    # Плохо
    expr.__str__()
    expr.__add__(other)
    
    # Лучше
    str(expr)
    expr + other
  • Не используй type для проверки того, что объект --- экземпляр некоторого класса. Для этого больше подходит функция isinstance.

    # Плохо
    type(instance) == Point
    type(instance) is Point
    
    # Лучше
    isinstance(instance, Point)

Исключения

  • Минимизируй размер блоков try и with.

  • Чтобы поймать любое исключение используй except Exception, а не except BaseException или просто except.

  • Указывай наиболее специфичный тип исключения в блоке except.

    # Плохо
    try:
        mapping[key]
    except Exception:
        ...
    
    # Лучше
    try:
        mapping[key]
    except KeyError:
        ...
  • Наследуй собственные исключения от Exception, а не от BaseException.

  • Используй менеджеры контекста вместо try-finally.

    # Плохо
    handle = open("path/to/file")
    try:
        do_something(handle)
    finally:
        handle.close()
    
    # Лучше
    with open("path/to/file") as handle:
        do_something(handle)