Документация Python неофициальный перевод
Содержание страницы

Идиомы и анти-идиомы в PythonIdioms and Anti-Idioms in Python

Автор:Moshe Zadka

Этот документ находится в общественном достоянии.

Аннотация

Этот документ можно рассматривать как дополнение к руководству. В нём показано, как использовать Python и, что ещё важнее, как не использовать Python.

Конструкции языка, которые не следует использоватьLanguage Constructs You Should Not Use

Хотя в Python относительно мало «подводных камней» по сравнению с другими языками, в нём всё ещё есть конструкции, которые полезны только в крайних случаях или откровенно опасны.

from module import *

Внутри определений функцийInside Function Definitions

from module import * недопустим внутри определений функций. Хотя во многих версиях Python эта недопустимость не проверяется, это не делает конструкцию более допустимой – не более, чем наличие умного адвоката делает человека невиновным. Никогда не используйте её подобным образом. Даже в версиях, где она принималась, это замедляло выполнение функции, поскольку компилятор не мог быть уверен, какие имена являются локальными, а какие глобальными. В Python 2.1 эта конструкция вызывает предупреждения, а иногда и ошибки.

На уровне модуляAt Module Level

Хотя использовать from module import * на уровне модуля допустимо, обычно это плохая идея. Во-первых, это лишает Python важного свойства – возможности узнать, где определён каждый глобальный идентификатор, с помощью простой функции поиска в вашем любимом редакторе. Вы также создаёте себе проблемы в будущем, если какой-либо модуль обрастёт дополнительными функциями или классами.

Один из самых ужасных вопросов, задаваемых в новостной группе: почему этот код:

f = open("www")
f.read()

Не работает. Конечно, он прекрасно работает (если у вас есть файл с именем «www».) Но он не работает, если где-то в модуле присутствует оператор from os import *. Модуль os содержит функцию open(), которая возвращает целое число. Хотя это очень полезно, переопределение встроенной функции – одно из наименее полезных его свойств.

Помните: вы никогда не можете знать наверняка, какие имена экспортирует модуль, поэтому либо берите то, что нужно – from module import name1, name2, либо оставляйте их в модуле и обращайтесь по мере необходимости – import module;print module.name.

Когда это вполне допустимоWhen It Is Just Fine

Бывают ситуации, когда from module import * вполне уместно:

  • Интерактивная оболочка. Например, from math import * превращает Python в отличный научный калькулятор.
  • При расширении модуля на C модулем на Python.
  • Когда модуль заявляет, что он безопасен для from import *.

Неукрашенные exec, execfile() и им подобныеUnadorned exec, execfile() and friends

Слово «неукрашенные» означает использование без явного словаря, и в этом случае эти конструкции выполняют код в текущем окружении. Это опасно по тем же причинам, что и from import *: может перезаписать переменные, на которые вы рассчитываете, и нарушить работу остального кода. Просто не делайте так.

Плохие примеры:

>>> for name in sys.argv[1:]:
>>>     exec "%s=1" % name
>>> def func(s, **kw):
>>>     for var, val in kw.items():
>>>         exec "s.%s=val" % var  # недопустимо!
>>> execfile("handler.py")
>>> handle()

Хорошие примеры:

>>> d = {}
>>> for name in sys.argv[1:]:
>>>     d[name] = 1
>>> def func(s, **kw):
>>>     for var, val in kw.items():
>>>         setattr(s, var, val)
>>> d={}
>>> execfile("handle.py", d, d)
>>> handle = d['handle']
>>> handle()

from module import name1, name2

Это «нельзя», которое гораздо слабее предыдущих «нельзя», но всё равно то, чего не следует делать, если нет веских причин. Обычно это плохая идея, потому что у вас внезапно появляется объект, живущий в двух разных пространствах имён. Когда привязка в одном пространстве имён меняется, привязка в другом не меняется, поэтому между ними возникает расхождение. Это происходит, например, при перезагрузке модуля или изменении определения функции во время выполнения.

Плохой пример:

# foo.py
a = 1

# bar.py
from foo import a
if something():
    a = 2 # опасность: foo.a != a

Хороший пример:

# foo.py
a = 1

# bar.py
import foo
if something():
    foo.a = 2

except:

В Python есть конструкция except:, которая перехватывает все исключения. Поскольку любая ошибка в Python вызывает исключение, это приводит к тому, что многие ошибки программирования выглядят как проблемы времени выполнения, и затрудняет процесс отладки.

Следующий код показывает наглядный пример:

try:
    foo = opne("file") # опечатка в "open"
except:
    sys.exit("could not open file!")

Вторая строка вызывает исключение NameError, которое перехватывается блоком except. Программа завершится, и вы не будете знать, что это не имеет никакого отношения к читаемости "file".

Лучше написать так:

try:
    foo = opne("file") # будет изменён на "open", как только будет запущен
except IOError:
    sys.exit("could not open file")

Есть несколько ситуаций, в которых конструкция except: полезна: например, в фреймворке при выполнении колбэков – желательно не позволять ни одному колбэку нарушать работу фреймворка.

ИсключенияExceptions

Исключения – полезная возможность Python. Стоит научиться возбуждать их всякий раз, когда происходит что-то неожиданное, и перехватывать только там, где можно на них повлиять.

Ниже приведён очень популярный антипаттерн.

def get_status(file):
    if not os.path.exists(file):
        print "file not found"
        sys.exit(1)
    return open(file).readline()

Рассмотрим случай, когда файл удаляется между вызовом os.path.exists() и вызовом open(). Это означает, что последняя строка вызовет исключение IOError. То же самое произойдёт, если file существует, но нет прав на чтение. Поскольку при тестировании на обычной машине с существующими и несуществующими файлами ошибка не проявляется, результаты тестирования будут выглядеть нормально, и код будет выпущен. Тогда необработанное исключение IOError доходит до пользователя, который вынужден смотреть на некрасивую трассировку.

Вот более правильный способ:

def get_status(file):
    try:
        return open(file).readline()
    except (IOError, OSError):
        print "file not found"
        sys.exit(1)

В этой версии либо файл открывается и строка читается (так что это работает даже на ненадёжных соединениях NFS или SMB), либо выводится сообщение и приложение завершается.

Тем не менее, get_status() делает слишком много предположений – что она будет использоваться только в коротком скрипте, а не, скажем, в долго работающем сервере. Конечно, вызывающий код мог бы сделать что-то вроде

try:
    status = get_status(log)
except SystemExit:
    status = None

Поэтому старайтесь использовать как можно меньше конструкций except в коде – обычно они нужны как универсальный обработчик в main() или внутри вызовов, которые всегда должны завершаться успешно.

Итак, вероятно, лучший вариант:

def get_status(file):
    return open(file).readline()

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

Последний вариант тоже не очень хорош – из-за особенностей реализации файл не будет закрыт при возникновении исключения, пока не завершится обработчик, а возможно, и вовсе не будет закрыт в реализациях, отличных от C (например, Jython).

def get_status(file):
    with open(file) as fp:
        return fp.readline()

Использование встроенных средствUsing the Batteries

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

Полезный модуль, о котором мало кто знает – это os.path. Он всегда содержит правильные операции с путями для вашей операционной системы и обычно намного лучше того, что вы придумаете сами.

Сравните:

# фу!
return dir+"/"+file
# лучше
return os.path.join(dir, file)

Ещё полезные функции из os.path: basename(), dirname() и splitext().

Есть также много полезных встроенных функций, о которых люди почему-то не знают: min() и max() могут найти минимум/максимум любой последовательности со сравниваемой семантикой, например, но многие пишут свои собственные max()/min(). Ещё одна очень полезная функция – reduce(). Классическое использование reduce() выглядит примерно так

import sys, operator
nums = map(float, sys.argv[1:])
print reduce(operator.add, nums)/len(nums)

Этот маленький скрипт выводит среднее арифметическое всех чисел, переданных в командной строке. reduce() суммирует все числа, а остальное – лишь небольшая пред- и постобработка.

Кстати, обратите внимание, что float(), int() и long() принимают строковые аргументы и потому подходят для разбора – при условии, что вы готовы обработать ValueError, которое они возбуждают.

Использование обратного слеша для продолжения инструкцийUsing Backslash to Continue Statements

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

if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \
   calculate_number(10, 20) != forbulate(500, 360):
      pass

Следует понимать, что это опасно: случайный пробел после \ сделает эту строку неверной, а случайные пробелы, как известно, трудно заметить в редакторах. В этом случае будет хотя бы синтаксическая ошибка, но если бы код был:

value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \
        + calculate_number(10, 20)*forbulate(500, 360)

то ошибка была бы неявной.

Обычно гораздо лучше использовать неявное продолжение внутри скобок:

Эта версия защищена от ошибок:

value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]
        + calculate_number(10, 20)*forbulate(500, 360))