doanddont.md
1> **Источник:** https://python-all.ru/3.0/howto/doanddont.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Идиомы и анти-идиомы в Python89| Автор: | Moshe Zadka |10| --- | --- |1112Этот документ находится в общественном достоянии.1314Аннотация1516Этот документ можно рассматривать как дополнение к руководству. В нём показано, как использовать Python и, что ещё важнее, как *не* использовать Python.1718## Конструкции языка, которые не следует использовать1920Хотя в Python относительно мало «подводных камней» по сравнению с другими языками, в нём всё ещё есть конструкции, которые полезны только в крайних случаях или откровенно опасны.2122### from module import \*2324#### Внутри определений функций2526`from module import *` is *invalid* inside function definitions. While many versions of Python do not check for the invalidity, it does not make it more valid, no more then having a smart lawyer makes a man innocent. Do not use it like that ever. Even in versions where it was accepted, it made the function execution slower, because the compiler could not be certain which names are local and which are global. In Python 2.1 this construct causes warnings, and sometimes even errors.2728#### На уровне модуля2930Хотя использовать `from module import *` на уровне модуля допустимо, обычно это плохая идея. Во-первых, это лишает Python важного свойства – возможности узнать, где определён каждый глобальный идентификатор, с помощью простой функции поиска в вашем любимом редакторе. Вы также создаёте себе проблемы в будущем, если какой-либо модуль обрастёт дополнительными функциями или классами.3132Один из самых ужасных вопросов, задаваемых в новостной группе: почему этот код:3334```python35f = open("www")36f.read()37```3839не работает. Конечно, он прекрасно работает (при условии, что у вас есть файл с именем «www»). Но он не работает, если где-то в модуле присутствует инструкция `from os import *`. Модуль [`os`](https://python-all.ru/3.0/library/os.html#module-os) содержит функцию [`open()`](https://python-all.ru/3.0/library/functions.html#open), которая возвращает целое число. Хотя это весьма полезно, затенение встроенных функций – одно из наименее полезных её свойств.4041Помните: вы никогда не можете знать наверняка, какие имена экспортирует модуль, поэтому либо берите то, что нужно – `from module import name1, name2`, либо храните их в модуле и обращайтесь по мере необходимости – `import module; print(module.name)`.4243#### Когда это вполне допустимо4445Бывают ситуации, когда `from module import *` вполне уместно:4647- Интерактивная оболочка. Например, `from math import *` превращает Python в отличный научный калькулятор.48- При расширении модуля на C модулем на Python.49- Когда модуль заявляет, что он безопасен для `from import *`.5051### from module import name1, name25253Это правило «не делай» гораздо менее строгое, чем предыдущие, но всё равно не стоит его нарушать без веских причин. Дело в том, что у вас внезапно появляется объект, живущий в двух разных пространствах имён. Когда привязка в одном пространстве имён меняется, в другом она остаётся прежней, что приводит к расхождению. Так бывает, например, при перезагрузке модуля или изменении определения функции во время выполнения.5455Плохой пример:5657```python58# foo.py59a = 16061# bar.py62from foo import a63if something():64 a = 2 # опасность: foo.a != a65```6667Хороший пример:6869```python70# foo.py71a = 17273# bar.py74import foo75if something():76 foo.a = 277```7879### except:8081В Python есть конструкция `except:`, которая перехватывает все исключения. Поскольку *любая* ошибка в Python вызывает исключение, это приводит к тому, что многие ошибки программирования выглядят как проблемы времени выполнения, и затрудняет процесс отладки.8283Следующий код показывает наглядный пример:8485```python86try:87 foo = opne("file") # опечатка в "open"88except:89 sys.exit("could not open file!")90```9192Вторая строка вызывает исключение [`NameError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.NameError), которое перехватывается блоком except. Программа завершится, и вы не будете знать, что это не имеет никакого отношения к читаемости `"file"`.9394Лучше написать так:9596```python97try:98 foo = opne("file") # будет изменён на "open", как только будет запущен99except IOError:100 sys.exit("could not open file")101```102103Есть несколько ситуаций, в которых конструкция `except:` полезна: например, в фреймворке при выполнении колбэков – желательно не позволять ни одному колбэку нарушать работу фреймворка.104105## Исключения106107Исключения – полезная возможность Python. Стоит научиться возбуждать их всякий раз, когда происходит что-то неожиданное, и перехватывать только там, где можно на них повлиять.108109Ниже приведён очень популярный антипаттерн.110111```python112def get_status(file):113 if not os.path.exists(file):114 print("file not found")115 sys.exit(1)116 return open(file).readline()117```118119Рассмотрим случай, когда файл удаляется между вызовом [`os.path.exists()`](https://python-all.ru/3.0/library/os.path.html#os.path.exists) и вызовом [`open()`](https://python-all.ru/3.0/library/functions.html#open). Это означает, что последняя строка вызовет исключение [`IOError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.IOError). То же самое произойдёт, если *file* существует, но нет прав на чтение. Поскольку при тестировании на обычной машине с существующими и несуществующими файлами ошибка не проявляется, результаты тестирования будут выглядеть нормально, и код будет выпущен. Тогда необработанное исключение [`IOError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.IOError) доходит до пользователя, который вынужден смотреть на некрасивую трассировку.120121Вот более правильный способ:122123```python124def get_status(file):125 try:126 return open(file).readline()127 except (IOError, OSError):128 print("file not found")129 sys.exit(1)130```131132В этой версии либо файл открывается и строка читается (так что это работает даже на ненадёжных соединениях NFS или SMB), либо выводится сообщение и приложение завершается.133134Тем не менее, `get_status()` делает слишком много предположений – что она будет использоваться только в коротком скрипте, а не, скажем, в долго работающем сервере. Конечно, вызывающий код мог бы сделать что-то вроде135136```python137try:138 status = get_status(log)139except SystemExit:140 status = None141```142143Поэтому старайтесь использовать как можно меньше конструкций `except` в коде – обычно они нужны как универсальный обработчик в `main()` или внутри вызовов, которые всегда должны завершаться успешно.144145Итак, вероятно, лучший вариант:146147```python148def get_status(file):149 return open(file).readline()150```151152Вызывающий код может обработать исключение, если захочет (например, если он перебирает несколько файлов в цикле), или просто позволить исключению всплыть к *своему* вызывающему.153154Последний вариант тоже не очень хорош – из-за особенностей реализации файл не будет закрыт при возникновении исключения, пока не завершится обработчик, а возможно, и вовсе не будет закрыт в реализациях, отличных от C (например, Jython).155156```python157def get_status(file):158 fp = open(file)159 try:160 return fp.readline()161 finally:162 fp.close()163```164165## Использование встроенных средств166167Время от времени кто-то снова пытается написать что-то для стандартной библиотеки Python, обычно плохо. Хотя у некоторых модулей интерфейс оставляет желать лучшего, обычно гораздо лучше использовать богатую стандартную библиотеку и типы данных, входящие в Python, чем изобретать свои собственные.168169Полезный модуль, о котором очень мало кто знает, – это [`os.path`](https://python-all.ru/3.0/library/os.path.html#module-os.path). Он всегда содержит правильные операции с путями для вашей операционной системы и обычно будет гораздо лучше того, что вы придумаете сами.170171Сравните:172173```python174# фу!175return dir+"/"+file176# лучше177return os.path.join(dir, file)178```179180Ещё полезные функции из [`os.path`](https://python-all.ru/3.0/library/os.path.html#module-os.path): `basename()`, `dirname()` и `splitext()`.181182Есть также много полезных встроенных функций, о которых люди по какой-то причине не знают: [`min()`](https://python-all.ru/3.0/library/functions.html#min) и [`max()`](https://python-all.ru/3.0/library/functions.html#max) могут находить минимум/максимум любой последовательности со сравниваемой семантикой, например, но многие пишут свои собственные [`max()`](https://python-all.ru/3.0/library/functions.html#max)/[`min()`](https://python-all.ru/3.0/library/functions.html#min). Ещё одна очень полезная функция – [`functools.reduce()`](https://python-all.ru/3.0/library/functools.html#functools.reduce). Классическое использование `reduce()` выглядит примерно так:183184```python185import sys, operator, functools186nums = list(map(float, sys.argv[1:]))187print(functools.reduce(operator.add, nums) / len(nums))188```189190Этот маленький скрипт выводит среднее значение всех чисел, переданных в командной строке. `reduce()` суммирует все числа, а остальное – просто подготовка и обработка результатов.191192Кстати, обратите внимание, что [`float()`](https://python-all.ru/3.0/library/functions.html#float) и [`int()`](https://python-all.ru/3.0/library/functions.html#int) принимают аргументы строкового типа и поэтому подходят для разбора – при условии, что вы готовы обрабатывать [`ValueError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.ValueError), которое они вызывают.193194## Использование обратного слеша для продолжения инструкций195196Поскольку Python рассматривает перевод строки как завершитель инструкции, и поскольку инструкции часто бывают слишком длинными, чтобы поместить их в одну строку, многие делают так:197198```python199if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \200 calculate_number(10, 20) != forbulate(500, 360):201 pass202```203204Следует понимать, что это опасно: случайный пробел после `\` сделает эту строку неверной, а случайные пробелы, как известно, трудно заметить в редакторах. В этом случае будет хотя бы синтаксическая ошибка, но если бы код был:205206```python207value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \208 + calculate_number(10, 20)*forbulate(500, 360)209```210211то ошибка была бы неявной.212213Обычно гораздо лучше использовать неявное продолжение внутри скобок:214215Эта версия защищена от ошибок:216217```python218value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]219 + calculate_number(10, 20)*forbulate(500, 360))220```221