> **Источник:** https://python-all.ru/2.6/howto/doanddont.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# Идиомы и анти-идиомы в Python

| Автор: | Moshe Zadka |
| --- | --- |

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

Аннотация

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

## Конструкции языка, которые не следует использовать

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

### from module import \*

#### Внутри определений функций

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

#### На уровне модуля

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

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

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

Не работает. Конечно, он прекрасно работает (если у вас есть файл с именем «www».) Но он не работает, если где-то в модуле присутствует оператор `from os import *`. Модуль [`os`](https://python-all.ru/2.6/library/os.html#module-os) содержит функцию [`open()`](https://python-all.ru/2.6/library/functions.html#open), которая возвращает целое число. Хотя это очень полезно, переопределение встроенной функции – одно из наименее полезных его свойств.

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

#### Когда это вполне допустимо

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

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

### Неукрашенные [`exec`](https://python-all.ru/2.6/reference/simple_stmts.html#exec), [`execfile()`](https://python-all.ru/2.6/library/functions.html#execfile) и им подобные

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

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

```python
>>> 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()
```

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

```python
>>> 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

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

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

```python
# foo.py
a = 1

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

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

```python
# foo.py
a = 1

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

### except:

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

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

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

Вторая строка вызывает исключение [`NameError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.NameError), которое перехватывается блоком except. Программа завершится, и вы не будете знать, что это не имеет никакого отношения к читаемости `"file"`.

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

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

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

## Исключения

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

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

```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()`](https://python-all.ru/2.6/library/os.path.html#os.path.exists) и вызовом [`open()`](https://python-all.ru/2.6/library/functions.html#open). Это означает, что последняя строка вызовет исключение [`IOError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.IOError). То же самое произойдёт, если *file* существует, но нет прав на чтение. Поскольку при тестировании на обычной машине с существующими и несуществующими файлами ошибка не проявляется, результаты тестирования будут выглядеть нормально, и код будет выпущен. Тогда необработанное исключение [`IOError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.IOError) доходит до пользователя, который вынужден смотреть на некрасивую трассировку.

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

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

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

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

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

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

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

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

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

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

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

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

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

Полезный модуль, о котором мало кто знает – это [`os.path`](https://python-all.ru/2.6/library/os.path.html#module-os.path). Он всегда содержит правильные операции с путями для вашей операционной системы и обычно намного лучше того, что вы придумаете сами.

Сравните:

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

Ещё полезные функции из [`os.path`](https://python-all.ru/2.6/library/os.path.html#module-os.path): `basename()`, `dirname()` и `splitext()`.

Есть также много полезных встроенных функций, о которых люди почему-то не знают: [`min()`](https://python-all.ru/2.6/library/functions.html#min) и [`max()`](https://python-all.ru/2.6/library/functions.html#max) могут найти минимум/максимум любой последовательности со сравниваемой семантикой, например, но многие пишут свои собственные [`max()`](https://python-all.ru/2.6/library/functions.html#max)/[`min()`](https://python-all.ru/2.6/library/functions.html#min). Ещё одна очень полезная функция – [`reduce()`](https://python-all.ru/2.6/library/functions.html#reduce). Классическое использование [`reduce()`](https://python-all.ru/2.6/library/functions.html#reduce) выглядит примерно так

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

Этот маленький скрипт выводит среднее арифметическое всех чисел, переданных в командной строке. [`reduce()`](https://python-all.ru/2.6/library/functions.html#reduce) суммирует все числа, а остальное – лишь небольшая пред- и постобработка.

Кстати, обратите внимание, что [`float()`](https://python-all.ru/2.6/library/functions.html#float), [`int()`](https://python-all.ru/2.6/library/functions.html#int) и [`long()`](https://python-all.ru/2.6/library/functions.html#long) принимают строковые аргументы и потому подходят для разбора – при условии, что вы готовы обработать [`ValueError`](https://python-all.ru/2.6/library/exceptions.html#exceptions.ValueError), которое они возбуждают.

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

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

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

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

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

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

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

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

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