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

doanddont.md

221 строк · 17.6 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.1/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 *` *недопустим* внутри определений функций. Хотя во многих версиях Python эта недопустимость не проверяется, это не делает конструкцию более допустимой – не более, чем наличие умного адвоката делает человека невиновным. Никогда не используйте её подобным образом. Даже в версиях, где она принималась, это замедляло выполнение функции, поскольку компилятор не мог быть уверен, какие имена являются локальными, а какие глобальными. В Python 2.1 эта конструкция вызывает предупреждения, а иногда и ошибки.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.1/library/os.html#module-os) содержит функцию [`open()`](https://python-all.ru/3.1/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 порождает исключение, использование `except:` может превратить многие ошибки программирования в проблемы времени выполнения, что затрудняет отладку.8283Следующий код отлично демонстрирует, почему это плохо:8485```python86try:87    foo = opne("file") # опечатка в "open"88except:89    sys.exit("could not open file!")90```9192Вторая строка вызывает [`NameError`](https://python-all.ru/3.1/library/exceptions.html#NameError), которое перехватывается блоком except. Программа завершится, и выведенное сообщение об ошибке заставит вас думать, что проблема в читаемости `"file"`, хотя на самом деле настоящая ошибка не имеет к `"file"` никакого отношения.9394Более правильный способ написать вышеприведённый код –9596```python97try:98    foo = opne("file")99except IOError:100    sys.exit("could not open file")101```102103При выполнении этого кода Python выдаст трассировку с указанием [`NameError`](https://python-all.ru/3.1/library/exceptions.html#NameError), и сразу станет очевидно, что нужно исправить.104105Поскольку `except:` перехватывает *все* исключения, включая [`SystemExit`](https://python-all.ru/3.1/library/exceptions.html#SystemExit), [`KeyboardInterrupt`](https://python-all.ru/3.1/library/exceptions.html#KeyboardInterrupt) и [`GeneratorExit`](https://python-all.ru/3.1/library/exceptions.html#GeneratorExit) (которое не является ошибкой и обычно не должно перехватываться пользовательским кодом), использование голого `except:` почти никогда не является хорошей идеей. В ситуациях, где необходимо перехватывать все «обычные» исключения, например, в среде, запускающей колбэки, можно перехватывать базовый класс для всех обычных исключений – [`Exception`](https://python-all.ru/3.1/library/exceptions.html#Exception).106107## Исключения108109Исключения – полезная возможность Python. Стоит научиться возбуждать их всякий раз, когда происходит что-то неожиданное, и перехватывать только там, где можно на них повлиять.110111Ниже приведён очень популярный антипаттерн.112113```python114def get_status(file):115    if not os.path.exists(file):116        print("file not found")117        sys.exit(1)118    return open(file).readline()119```120121Рассмотрим случай, когда файл удаляется между вызовом [`os.path.exists()`](https://python-all.ru/3.1/library/os.path.html#os.path.exists) и вызовом [`open()`](https://python-all.ru/3.1/library/functions.html#open). В этом случае последняя строка вызовет исключение [`IOError`](https://python-all.ru/3.1/library/exceptions.html#IOError). То же самое произойдёт если *file* существует, но нет прав на чтение. Поскольку тестирование на обычной машине на существующих и несуществующих файлах не выявляет ошибок, результаты тестов будут казаться нормальными, и код попадёт в релиз. Позже необработанное [`IOError`](https://python-all.ru/3.1/library/exceptions.html#IOError) (или, возможно, другое [`EnvironmentError`](https://python-all.ru/3.1/library/exceptions.html#EnvironmentError)) доходит до пользователя, который видит уродливый traceback.122123Вот несколько более удачный способ.124125```python126def get_status(file):127    try:128        return open(file).readline()129    except EnvironmentError as err:130        print("Unable to open file: {}".format(err))131        sys.exit(1)132```133134В этой версии *либо* файл открывается и строка читается (поэтому он работает даже на ненадёжных NFS или SMB-соединениях), либо выводится сообщение об ошибке, содержащее всю доступную информацию о причине неудачи открытия, и приложение завершается.135136Однако даже эта версия `get_status()` содержит слишком много допущений – что она будет использоваться только в коротком скрипте, а не, скажем, в долго работающем сервере. Конечно, вызывающий код мог бы сделать что-то вроде137138```python139try:140    status = get_status(log)141except SystemExit:142    status = None143```144145Но есть способ лучше. Следует стараться использовать как можно меньше блоков `except` в коде – те, которые всё же используются, обычно должны быть внутри вызовов, которые всегда должны завершаться успешно, или как универсальный обработчик в главной функции.146147Итак, ещё более удачная версия `get_status()` – это, вероятно,148149```python150def get_status(file):151    return open(file).readline()152```153154Вызывающий код может обработать исключение, если захочет (например, если он перебирает несколько файлов в цикле), или просто позволить исключению всплыть до *своего* вызывающего кода.155156Но последняя версия всё ещё содержит серьёзную проблему – из-за особенностей реализации в CPython файл не будет закрыт при возникновении исключения до завершения обработчика исключения; и, что хуже, в других реализациях (например, Jython) он может вообще не закрыться, независимо от того, возникло исключение или нет.157158Лучшая версия этой функции использует вызов `open()` в качестве менеджера контекста, что гарантирует закрытие файла сразу после возврата функции:159160```python161def get_status(file):162    with open(file) as fp:163        return fp.readline()164```165166## Использование встроенных средств167168Время от времени люди зачем-то снова пишут то, что уже есть в библиотеке Python, и обычно плохо. Хотя некоторые модули имеют неудачный интерфейс, в целом гораздо лучше использовать богатую стандартную библиотеку и типы данных, входящие в Python, чем изобретать свои собственные.169170Полезный модуль, о котором мало кто знает – это [`os.path`](https://python-all.ru/3.1/library/os.path.html#module-os.path). Он всегда содержит правильные операции с путями для вашей операционной системы и обычно намного лучше того, что вы придумаете сами.171172Сравните:173174```python175# фу!176return dir+"/"+file177# лучше178return os.path.join(dir, file)179```180181Ещё полезные функции из [`os.path`](https://python-all.ru/3.1/library/os.path.html#module-os.path): `basename()`, `dirname()` и `splitext()`.182183Существует также множество полезных встроенных функций, о которых люди почему-то не знают: [`min()`](https://python-all.ru/3.1/library/functions.html#min) и [`max()`](https://python-all.ru/3.1/library/functions.html#max) могут находить минимум/максимум любой последовательности со сравнимыми элементами, но многие пишут свои собственные [`max()`](https://python-all.ru/3.1/library/functions.html#max)/[`min()`](https://python-all.ru/3.1/library/functions.html#min). Ещё одна очень полезная функция – [`functools.reduce()`](https://python-all.ru/3.1/library/functools.html#functools.reduce), которая применяет бинарную операцию к последовательности, сворачивая её до одного значения. Например, вычисление факториала через последовательное умножение:184185```python186>>> n = 4187>>> import operator, functools188>>> functools.reduce(operator.mul, range(1, n+1))18924190```191192Что касается разбора чисел, имейте в виду, что [`float()`](https://python-all.ru/3.1/library/functions.html#float), [`int()`](https://python-all.ru/3.1/library/functions.html#int) и `long()` все принимают строковые аргументы и отклоняют некорректные строки, выбрасывая [`ValueError`](https://python-all.ru/3.1/library/exceptions.html#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