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

doanddont.md

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

1> **Источник:** https://python-all.ru/2.7/howto/doanddont.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Идиомы и анти-идиомы в Python89**Автор**1011Moshe Zadka1213Этот документ находится в общественном достоянии.1415Аннотация1617Этот документ можно рассматривать как дополнение к руководству. В нём показано, как использовать Python и, что ещё важнее, как *не* использовать Python.1819## Конструкции языка, которые не следует использовать2021Хотя в Python относительно мало «подводных камней» по сравнению с другими языками, в нём всё ещё есть конструкции, которые полезны только в крайних случаях или откровенно опасны.2223### from module import \*2425#### Внутри определений функций2627`from module import *` *недопустима* внутри определений функций. Хотя многие версии Python не проверяют эту недопустимость, от этого она не становится более допустимой – не более, чем наличие умного адвоката делает человека невиновным. Никогда не используйте её так. Даже в версиях, где она принималась, это замедляло выполнение функции, потому что компилятор не мог быть уверен, какие имена локальные, а какие глобальные. В Python 2.1 эта конструкция вызывает предупреждения, а иногда и ошибки.2829#### На уровне модуля3031Хотя использовать `from module import *` на уровне модуля допустимо, обычно это плохая идея. Во-первых, теряется важное свойство Python – можно узнать, где определено каждое имя верхнего уровня, с помощью простой функции «поиска» в вашем любимом редакторе. Вы также открываете себя для проблем в будущем, если какой-нибудь модуль обрастёт дополнительными функциями или классами.3233Один из самых ужасных вопросов, задаваемых в новостной группе, – почему этот код:3435```python36f = open("www")37f.read()38```3940не работает. Конечно, он отлично работает (при условии, что у вас есть файл с именем «www».) Но он не работает, если где-то в модуле присутствует оператор `from os import *`. Модуль [`os`](https://python-all.ru/2.7/library/os.html#module-os) содержит функцию [`open()`](https://python-all.ru/2.7/library/functions.html#open), которая возвращает целое число. Хотя она очень полезна, затенение встроенной функции – одно из наименее полезных её свойств.4142Помните, вы никогда не можете знать наверняка, какие имена экспортирует модуль, поэтому либо берите то, что вам нужно – `from module import name1, name2`, либо храните их в модуле и обращайтесь по мере необходимости – `import module;print module.name`.4344#### Когда это вполне допустимо4546Бывают ситуации, когда `from module import *` вполне уместно:4748- Интерактивная подсказка. Например, `from math import *` превращает Python в отличный научный калькулятор.49- При расширении модуля на C модулем на Python.50- Когда модуль позиционирует себя как безопасный для `from import *`.5152### Просто [`exec`](https://python-all.ru/2.7/reference/simple_stmts.html#exec), [`execfile()`](https://python-all.ru/2.7/library/functions.html#execfile) и подобные5354Слово «просто» (unadorned) означает использование без явного словаря, и в этом случае эти конструкции выполняют код в *текущем* окружении. Это опасно по тем же причинам, что и `from import *` – оно может перезаписать переменные, на которые вы рассчитываете, и испортить остальной код. Просто не делайте так.5556Плохие примеры:5758```python59>>> for name in sys.argv[1:]:60>>>     exec "%s=1" % name61>>> def func(s, **kw):62>>>     for var, val in kw.items():63>>>         exec "s.%s=val" % var  # недопустимо!64>>> execfile("handler.py")65>>> handle()66```6768Хорошие примеры:6970```python71>>> d = {}72>>> for name in sys.argv[1:]:73>>>     d[name] = 174>>> def func(s, **kw):75>>>     for var, val in kw.items():76>>>         setattr(s, var, val)77>>> d={}78>>> execfile("handle.py", d, d)79>>> handle = d['handle']80>>> handle()81```8283### from module import name1, name28485Это «не делай», которое гораздо слабее предыдущих «не делай», но всё же то, чего не следует делать, если нет веских причин. Причина, по которой это обычно плохая идея, заключается в том, что у вас внезапно появляется объект, который живёт в двух разных пространствах имён. Когда привязка в одном пространстве имён меняется, привязка в другом не меняется, поэтому между ними возникает расхождение. Это происходит, например, когда один модуль перезагружается или изменяет определение функции во время выполнения.8687Плохой пример:8889```python90# foo.py91a = 19293# bar.py94from foo import a95if something():96    a = 2 # опасность: foo.a != a97```9899Хороший пример:100101```python102# foo.py103a = 1104105# bar.py106import foo107if something():108    foo.a = 2109```110111### except:112113В Python есть конструкция `except:`, которая перехватывает все исключения. Поскольку *каждая* ошибка в Python порождает исключение, использование `except:` может превратить многие ошибки программирования в проблемы времени выполнения, что затрудняет отладку.114115Следующий код отлично демонстрирует, почему это плохо:116117```python118try:119    foo = opne("file") # опечатка в "open"120except:121    sys.exit("could not open file!")122```123124Вторая строка вызывает [`NameError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.NameError), которое перехватывается конструкцией except. Программа завершится, и сообщение об ошибке, которое она выведет, заставит вас думать, что проблема в читаемости `"file"`, хотя на самом деле настоящая ошибка не имеет к `"file"` никакого отношения.125126Более правильный способ написать вышеприведённый код –127128```python129try:130    foo = opne("file")131except IOError:132    sys.exit("could not open file")133```134135При запуске Python выдаст трассировку, показывающую [`NameError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.NameError), и сразу станет ясно, что нужно исправить.136137Поскольку `except:` перехватывает *все* исключения, включая [`SystemExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.SystemExit), [`KeyboardInterrupt`](https://python-all.ru/2.7/library/exceptions.html#exceptions.KeyboardInterrupt) и [`GeneratorExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.GeneratorExit) (которое не является ошибкой и обычно не должно перехватываться пользовательским кодом), использование голого `except:` почти никогда не является хорошей идеей. В ситуациях, когда нужно перехватить все «нормальные» ошибки, например, во фреймворке, запускающем колбэки, можно перехватить базовый класс для всех обычных исключений – [`Exception`](https://python-all.ru/2.7/library/exceptions.html#exceptions.Exception). К сожалению, в Python 2.x сторонний код может порождать исключения, не наследующие от [`Exception`](https://python-all.ru/2.7/library/exceptions.html#exceptions.Exception), поэтому в Python 2.x в некоторых случаях может потребоваться использовать голый `except:` и вручную повторно порождать исключения, которые не нужно перехватывать.138139## Исключения140141Исключения – полезная возможность Python. Стоит научиться возбуждать их всякий раз, когда происходит что-то неожиданное, и перехватывать только там, где можно на них повлиять.142143Ниже приведён очень популярный антипаттерн.144145```python146def get_status(file):147    if not os.path.exists(file):148        print "file not found"149        sys.exit(1)150    return open(file).readline()151```152153Рассмотрим случай, когда файл удаляется между вызовом [`os.path.exists()`](https://python-all.ru/2.7/library/os.path.html#os.path.exists) и вызовом [`open()`](https://python-all.ru/2.7/library/functions.html#open). В этом случае последняя строка вызовет [`IOError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.IOError). То же самое произойдёт, если *файл* существует, но нет прав на чтение. Поскольку тестирование на обычной машине на существующих и несуществующих файлах кажется безошибочным, результаты тестов будут выглядеть хорошо, и код будет выпущен. Позже необработанное [`IOError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.IOError) (или, возможно, другое [`EnvironmentError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.EnvironmentError)) дойдёт до пользователя, который увидит неприглядную трассировку.154155Вот несколько более удачный способ.156157```python158def get_status(file):159    try:160        return open(file).readline()161    except EnvironmentError as err:162        print "Unable to open file: {}".format(err)163        sys.exit(1)164```165166В этой версии *либо* файл открывается и строка читается (поэтому он работает даже на ненадёжных NFS или SMB-соединениях), либо выводится сообщение об ошибке, содержащее всю доступную информацию о причине неудачи открытия, и приложение завершается.167168Однако даже эта версия `get_status()` делает слишком много допущений – что она будет использоваться только в коротком скрипте, а не, скажем, в долго работающем сервере. Конечно, вызывающий код мог бы сделать что-то вроде169170```python171try:172    status = get_status(log)173except SystemExit:174    status = None175```176177Но есть способ лучше. Следует стараться использовать как можно меньше конструкций `except` в своём коде – те, что используются, обычно находятся внутри вызовов, которые всегда должны завершаться успешно, или как всеобъемлющий перехват в главной функции.178179Итак, ещё лучшая версия `get_status()`, вероятно,180181```python182def get_status(file):183    return open(file).readline()184```185186Вызывающий код может обработать исключение, если захочет (например, если он перебирает несколько файлов в цикле), или просто позволить исключению всплыть до *своего* вызывающего кода.187188Но последняя версия всё ещё содержит серьёзную проблему – из-за особенностей реализации в CPython файл не будет закрыт при возникновении исключения до завершения обработчика исключения; и, что хуже, в других реализациях (например, Jython) он может вообще не закрыться, независимо от того, возникло исключение или нет.189190Лучшая версия этой функции использует вызов `open()` в качестве менеджера контекста, что гарантирует закрытие файла сразу после возврата функции:191192```python193def get_status(file):194    with open(file) as fp:195        return fp.readline()196```197198## Использование встроенных средств199200Время от времени люди зачем-то снова пишут то, что уже есть в библиотеке Python, и обычно плохо. Хотя некоторые модули имеют неудачный интерфейс, в целом гораздо лучше использовать богатую стандартную библиотеку и типы данных, входящие в Python, чем изобретать свои собственные.201202Полезный модуль, о котором знают очень немногие, – это [`os.path`](https://python-all.ru/2.7/library/os.path.html#module-os.path). Он всегда выполняет корректные операции с путями для вашей операционной системы и обычно будет намного лучше всего, что вы придумаете сами.203204Сравните:205206```python207# фу!208return dir+"/"+file209# лучше210return os.path.join(dir, file)211```212213Ещё полезные функции в [`os.path`](https://python-all.ru/2.7/library/os.path.html#module-os.path): `basename()`, `dirname()` и `splitext()`.214215Есть также много полезных встроенных функций, о которых люди почему-то не знают: [`min()`](https://python-all.ru/2.7/library/functions.html#min) и [`max()`](https://python-all.ru/2.7/library/functions.html#max) могут найти минимум/максимум любой последовательности с сопоставимой семантикой, но многие пишут свои собственные [`max()`](https://python-all.ru/2.7/library/functions.html#max)/[`min()`](https://python-all.ru/2.7/library/functions.html#min). Ещё одна чрезвычайно полезная функция – [`reduce()`](https://python-all.ru/2.7/library/functions.html#reduce), которую можно использовать для многократного применения бинарной операции к последовательности, сводя её к одному значению. Например, вычислить факториал с помощью серии операций умножения:216217```python218>>> n = 4219>>> import operator220>>> reduce(operator.mul, range(1, n+1))22124222```223224Что касается разбора чисел, обратите внимание, что [`float()`](https://python-all.ru/2.7/library/functions.html#float), [`int()`](https://python-all.ru/2.7/library/functions.html#int) и [`long()`](https://python-all.ru/2.7/library/functions.html#long) все принимают строковые аргументы и будут отклонять некорректные строки, порождая [`ValueError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.ValueError).225226## Использование обратного слеша для продолжения инструкций227228Поскольку Python рассматривает перевод строки как завершитель инструкции, и поскольку инструкции часто длиннее, чем удобно помещать в одну строку, многие делают так:229230```python231if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \232   calculate_number(10, 20) != forbulate(500, 360):233      pass234```235236Следует понимать, что это опасно: случайный пробел после `\` сделает эту строку неверной, а случайные пробелы, как известно, трудно заметить в редакторах. В этом случае будет хотя бы синтаксическая ошибка, но если бы код был:237238```python239value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \240        + calculate_number(10, 20)*forbulate(500, 360)241```242243то ошибка была бы неявной.244245Обычно гораздо лучше использовать неявное продолжение внутри скобок:246247Эта версия защищена от ошибок:248249```python250value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]251        + calculate_number(10, 20)*forbulate(500, 360))252```253