RU | EN

Когда атакуют питоны: общие ошибки программистов на Python

В этой статье я приведу некоторые из самых обычных ошибок, которые делаются вновь и вновь и новыми и старыми программистами на Python, чтобы помочь Вам избежать их в вашей собственной работе.
Прежде всего я должен объяснить, что они приходят прямо от непосредственного опыта. Я зарабатываю на жизнь в качестве тренера Python. За прошлые семь лет я провел более чем 100 занятий о Python, более чем с 1 000 студентов - и наблюдал, как большинство из них делает те же самые ошибки. Таким образом, ошибки, которые я видел, новички Python делают сотни раз. Фактически, некоторые настолько обычны, что они неожиданно возникают, когда Вы только начинаете.
"Что это?", - говорите Вы: "Можно сделать много ошибок в программе на Python?" Да. Python может быть одним из самых простых и самых гибких языков программирования, но это - все еще язык программирования. Он все еще имеет синтаксис, типы данных и случайный темный угол, населяемый волшебниками по имени Тим.
Хорошие новости в том, что, как только Вы изучите Python, вы избегните многих ловушек естественно, благодаря чистому дизайну языка. Python имеет минимальный набор взаимодействий между его компонентами, что помогает уменьшить количество ошибок. Он имеет простой синтаксис, то есть меньше возможности сделать ошибку. И когда Вы действительно делаете ошибку, обнаружение ошибки во время выполнения программы Python и отчеты помогают Вам быстро обнаружить просчет.
Но программирование на Python все еще не автоматизированная задача, и предупрежденный - значит вооруженный. Давайте начнем повседневную работу. Следующие три группы разделов включают прагматику, кодирование и программирование в целом. Если Вы хотели бы больше почитать об обычных ошибках Python и о том как их избежать, обратитесь к книге O'Reilly Learning Python, 2nd Edition.

Прагматические ошибки

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

Вводите код Python в интерактивном режиме

Вы можете ввести только код Python, но не системные команды, в >>> командную строку. Не то, чтобы редко видеть людей работающих с emacs, ls или другими командами, но эти поманды не код Python. Есть способы выполнить системные команды изнутри кода Python (например, os.system и os.popen), но они не являются столь же прямыми, как простой непосредственный ввод команды. Если Вы хотите запустить программу на Python в интерактивном режиме, используйте import file, не системную команду python file.py.

Инструкция print требуется только в файлах

Поскольку интерактивный интерпретатор автоматически печатает результаты выражений, Вы не должны вводить полные инструкции print в интерактивном режиме. Это хорошая особенность, но помните, что в пределах файла кода, Вы вообще должны использовать print, чтобы видеть вывод.

Остерегайтесь автоматического назначения расширений файлу в Windows

Если Вы используете программу Notepad, чтобы кодировать файлы программ в Windows, выбирайте тип "Все Файлы", когда дело доходит до сохранения вашего файла, и давайте явно вашему файлу суффикс .py. Иначе, Notepad сохраняет ваш файл с расширением .txt, мешая выполняться в некоторых схемах запуска. Word и WordPad добавляют форматирующие символы по умолчанию, которые не входят в синтаксис Python. Как эмпирическое правило всегда выбирайте "Все Файлы" и сохраняйте как простой текст в Windows, или используйте боле благоприятные для программиста редакторы текста типа IDLE. В IDLE не забудьте печатать .py расширение файла, вручную сохраняя файл.

Ловушки щелчка на иконку файла в Windows

В Windows Вы можете запустить программный файл Python нажимая на его иконку, но это может привести к ошибкам. Прежде всего, окно вывода программы исчезает, как только программа заканчивается; чтобы сохранять его открытым, пробуйте добавить запрос raw_input() внизу файла. Кроме того, имейте в виду, что окно вывода пропадает, если есть ошибка в программе; чтобы видеть ваши сообщения об ошибках, выполняйте вашу программу другими способами - из системной командной строки, интерактивным импортом, с опциями меню IDLE и так далее.

Импорт работает только в первый раз

Вы можете запустить файл импортируя его в интерактивном режиме, но это работает однажды за сеанс; последующий импорт просто возвращает уже загруженный модуль. Чтобы вынудить Python перезагружать и повторно запускать код файла, вызовите вместо этого функцию reload(module). Убедитесь, что использовали круглые скобки для перезагрузки, но не импорта.

Пустые строки в интерактивном режиме

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

Ошибки кодирования

Как только Вы начинаете писать код на Питона всерьез, следующий пакет ловушек начинает становиться более опасным; они - ошибки, которые охватывают языковые особенности и часто заманивают программиста в ловушку.

Не забывайте ставить двоеточия

Это самая общая ошибка новичка: не забывайте печатать ":" в конце составных заголовков утверждений (первая строка в if, while, for и т.д.). Вы вероятно будете пропускать это сначала, но это скоро станет не осознаваемой привычкой. Как правило, 75 процентов студентов на занятиях были сожжены этим к концу дня.

Инициализируйте ваши переменные

В Питоне Вы не можете использовать имя в пределах выражения, пока ему не было назначено значение. Так сделано нарочно: это помогает предотвращать обычные ошибки вроде опечаток и избегает неоднозначного вопроса о том, чем должно быть автоматическое значение по умолчанию (0, None, "", [], ?). Не забудьте инициализировать счетчики 0, сумматоры списка [] и так далее.

Начинайте в первой строке

Убедитесь, что ввели невложенный код верхнего уровня полностью слева, в столбце 1. Это включает невложенный код в файлах модуля, так же как невложенный код при интерактивном режиме. Питон использует сдвиг, чтобы разграничить блоки вложенного кода, таким образом незаполненное пространство слева от вашего кода означает вложенный блок. Незаполненное пространство вообще говоря игнорируется всюду, за исключением сдвига.

Делайте отступы последовательно

Избегайте смешивания табуляций и пробелов в сдвиге данного отдельного блока, если Вы не знаете что каждая система, которая касается вашего кода, может сделать с табуляциями. Иначе, то, что Вы видите в вашем редакторе, возможно не то, что видит Python, когда это считает табуляции как пробелы. Более безопасно использовать только табуляции или только пробелы для каждого блока.

Всегда используйте круглые скобки, чтобы вызвать функцию

Вы должны добавить круглые скобки после имени функции, чтобы вызвать ее, требуются ли параметры или нет. Таким образом, используйте function(), не function. Функции Python - просто объекты, которые имеют специальный метод, вызов, который Вы вызываете с круглыми скобками. Как все объекты, они могут быть назначены переменным и использоваться косвенно: x = function; x().
В обучении Python это, кажется, происходит чаще всего с файлами. Обычно новички печатают file.close, чтобы закрыть файл, а не file.close(), потому что законно сослаться на функцию, не вызывая ее. Первая версия вызова функции без круглой скобки тихо завершается успешно, но не закрывает файл!

Не используйте расширения или пути в импортах

Используйте пути каталогов и расширения файла в системных командных строках (например, python dir/mod.py), но не в утверждениях import. Таким образом, говорите import mod, не import mod.py или import dir/mod.py. Практически это - вероятно вторая самая обычная ошибка новичка. Поскольку модули могут иметь другие суффиксы помимо .py (.pyc, например), использование к коде специфического суффикса не только неправильный синтаксис, это не имеет смысла.
Определенный для платформы синтаксис пути каталога исходит из ваших параметров настройки пути поиска файлов модуля, не утверждения import. Вы можете использовать точки в именах файла, чтобы обратиться к подкаталогам (например, import dir1.dir2.mod), но крайний левый каталог все еще должен быть найден через путь поиска файлов модуля, и никакой другой синтаксис пути не может появиться в импортах. Неправильное утверждение import mod.py Python считает импортом пакета mod, и затем пробует найти модуль py в пределах каталога mod, и это приводит к генерации потенциально запутывающего сообщения об ошибке.

Не кодируйте на Питоне как на С

Несколько напоминаний для C/C ++ программистов, плохо знакомых с Python:
Вы не обязаны вводить круглые скобки вокруг условий в if и while (например, if (X == 1):). Вы можете так делать, если это Вам нравится, так как любое выражение может быть включено в круглые скобки, но они являются полностью лишними в этом контексте.
Не заканчивайте все ваши утверждения точкой с запятой. Технически это законно делать в Python, но полностью бесполезно, если Вы не помещаете больше чем одно утверждение в одиночной линии (например, x=1; y=2; z=3).
Не внедряйте операторы присваивания в условие цикла while (например, while ((x=next() ! = NULL)). В Python не могут появиться утверждения, где ожидаются выражения, а присвоение не есть выражение.

Ошибки программирования

Наконец, вот некоторые из проблем, на которые Вы можете натолкнуться, когда начинаете работать с большими особенностями языка Python - типы данных, функции, модули, классы и т.п.. Из-за пространственных ограничений, этот раздел сокращен, особенно относительно расширенных понятий программирования; для остальной части истории, см. подсказки Learning Python, 2nd Edition.

Вызовы функций для открытия файлов не используют путь поиска файлов модуля

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

Методы определены для типов

Вы не можете использовать методы списка на строках и наоборот. Вообще, вызовы методов зависимы от типа данных, но встроенные функции могут работать с многими типами. Например, метод перстановки элементов списка воздействует только на списки, но функция len раотает с любым объектом имеющим длину.

Неизменяемые типы не могут быть изменены на месте

Помните, что Вы не можете изменить неизменный объект (например, кортеж, строка):

T = (1, 2, 3) T[2] = 4 # Ошибка

Создайте новый объект разрезанием, конкатенацией и так далее, и присвойте результат первоначальной переменной если нужно. Поскольку Питон автоматически возвращает неиспользованную память, это не столь расточительно, как это может казаться:

T = T[:2] + (4,) # Нормально: T становится (1, 2, 4)

Используйте простые циклы, вместо while или range

Когда Вы должны перебрать все элементы в последовательности объектов слева направо, простое условие для цикла for (например, for x in seq:) более просто закодировать, и обычно оно более быстро работает, чем while- или range- цикл на основе диапазона. Избегите искушения использовать range в for, если только Вы действительно не вынуждены это сделать; позвольте Python обрабатывать индексацию за Вас. Все три следующих цикла раьотают, но первый обычно лучше; в Python просто значит хорошо.

S = "lumberjack" for c in S: print c # самый простой вариант for i in range(len(S)): print S[i] # сложно i = 0 # сложно while i < len(S): print S[i]; i += 1

Не ожидайте, что функции изменят объекты Операции типа list.append() и list.sort() изменяют объект, но не возвращают объект, который был изменен (они возвращают None); вызывайте их не присваивая результат. Весьма обычно для новичков:

mylist = mylist.append(X)

чтобы попробовать получить результат append; наоборот, это присваивает mylist None, а не измененный список. Более детальный пример этого получается при попытке перебрать элементы словаря:

D = {...} for k in D.keys().sort(): print D[k]

Это почти работает - метод keys строит список keys, и метод sort заказывает его - но так как метод sort возвращает None, цикл терпит неудачу, потому что это конечном счете цикл по None (не последовательность). Чтобы закодировать это правильно, разбейте метод, вызываемый в утверждении:

Ks = D.keys() Ks.sort() for k in Ks: print D[k]

Приведение типов происходит только среди числовых типов

В Python выражение вроде 123 + 3.145 работает - он автоматически преобразовывает целое число в число с плавающей запятой и использует математику с плавающей запятой. С другой стороны, следующее терпит неудачу:

S = "42" I = 1 X = S + I # Ошибка типа данных

Это - также нарочно, потому что это неоднозначно: строка должна быть преобразована к числу (для добавления), или число к строке (для конкатенации)? В Python мы говорим, что явный лучше чем неявный (ЯЛЧН), таким образом Вы должны выпольнить преобразование вручную:

X = int(S) + I # Выполнить сложение: 43 X = S + str(I) # Выполнить конкатенацию: "421"

Циклические структуры данных могут вызвать циклы

Хотя это довольно редко встречается на практике, но если объект содержит ссылку на себя, он назваестся циклическим объектом. Питон печатает [...] всякий раз, когда обнаруживает цикл в объекте, вместо того, чтобы застрять в бесконечном цикле:

>>> L = ['grail'] # Добавить ссылку на L >>> L.append(L) # Генерирует цикл в объекте >>> L ['grail', [...]]

Помимо понимания, что эти три точки представляют цикл в объекте, этот случай стоит знать, потому что циклические структуры могут заставить собственный код впадать в неожиданные циклы, если Вы не предупреждаете их. Если нужно, сохраните список или словарь уже посещенных элементов, и отметьте, что Вы достигли цикла в объекте.

Присвоение создает ссылки, не копии

Это - основное понятие Python, которое может вызвать проблемы, когда такое поведение не ожидается. В следующем примере на объект списка, с именем L ссылаются и от L, и изнутри списка М. Измененяя L изменяется также то, на что ссылается М, потому что есть две ссылки на тот же самый объект:

>>> L = [1, 2, 3] # Разделяемый объект типа список >>> M = ['X', L, 'Y'] # Включить ссылку на L >>> M ['X', [1, 2, 3], 'Y'] >>> L[1] = 0 # также изменяет и M >>> M ['X', [1, 0, 3], 'Y']

Этот эффект обычно становится важным только в больших программах, и разделяемые ссылки - обычно точно, что Вы хотите. Если нет, Вы можете избежать совместного использования объектов, копируя их явно; для списков, Вы можете сделать копию верхнего уровня при использовании среза пустых пределов:

>>> L = [1, 2, 3] >>> M = ['X', L[:], 'Y'] # Embed a copy of L >>> L[1] = 0 # Change only L, not M >>> L [1, 0, 3] >>> M ['X', [1, 2, 3], 'Y']

Срез ограничивает значение по умолчанию до 0 и длину срезаемой последовательности. Если оба опущены, срез извлекает каждый элемент последовательности, и так делает копию верхнего уровня (новый, неразделяемый объект). Для словарей, используйте метод dict.copy().

Местные имена обнаруживаются статически

Python классифицирует имена, назначенные в теле функции как местные по умолчанию; они живут в области видимости функции и существуют только пока функция выполняется. Технически, Python обнаруживает локальные имена статически, когда это собирает код def, а не обращая внимание на присвоения во время выполнения. Это может также привести к недораумениям, если этот факт неясен. Например, смотрите, что случается, если Вы добавляете присвоение переменной после ссылки:

>>> X = 99 >>> def func(): ... print X # пока еще не существует ... X = 88 # Делает X местным именем во всем def ... >>> func( ) # Ошибка!

Вы получаете ошибку неопределенного имени, но причина тонкая. Собирая этот код, Python видит присвоение X и решает, что X будет местным именем всюду в функции. Но позже, когда функция фактически выполнена, присвоение все еще не выполнено, когда выполняется печать, таким образом Python выдает ошибку неопределенного имени.
Действительно, предыдущий пример неоднозначен: Вы хотели печатать глобальный X и затем создавать местный X, или действительно ли это подлинная ошибка программирования? Если Вы действительно хотите печатать глобальный X, Вы должны объявить это в глобальном утверждении, или сослаться на это через имя модуля.

Значения по умолчанию и изменяемые объекты

Значения параметра по умолчанию оцениваются и сохраняются однажды, когда утверждение def выполнено, не каждый раз, когда функцию вызывают. Это обычно то, чего Вы хотите, но так как значения по умолчанию сохраняют тот же самый объект между запросами, Вы должны быть внимательными при изменении изменяемых значений по умолчанию. Например, следующая функция использует пустой список как значение по умолчанию и затем изменяет его каждый раз, когда функцию вызывают:

>>> def saver(x=[]): # Сохраняет объект типа список ... x.append(1) # и изменяет его каждый раз ... print x ... >>> saver([2]) # Значение по умолчанию не используется [2, 1] >>> saver() # Значение по умолчанию используется [1] >>> saver() # Увелеичвается при каждом вызове! [1, 1] >>> saver() [1, 1, 1]

Некоторые видят это поведение как особенность - потому что значения по умолчанию измяемых параметров сохраняют их состояние между вызовами функций, в некоторых случаях они могут служить как статические местные переменные функции в языке C. Однако, это может казаться странным в первый раз, когда Вы сталкиваетесь с этим, и есть более простые способы сохранить состояние между запросами в Питоне (например, классы).
Чтобы избежать этого поведения, сделайте копии значения по умолчанию в начале тела функции срезами или методами, или переместите выражение значения по умолчанию в тело функции; пока значение постоянно находится в коде, который выполняется каждый раз при вызове функции, Вы получите новый объект каждый раз:

>>> def saver(x=None): ... if x is None: x = [] # Параметры не были переданы? ... x.append(1) # Изменяет новый список ... print x ... >>> saver([2]) # Значение по умолчанию не используется [2, 1] >>> saver() # Теперь значение не увеличивается при каждом вызове [1] >>> saver() [1]

Другие обычные ловушки программирования

Вот быстрый обзор других ловушек, для которых мы не имеем пространства подробного описания:
Порядок утверждений имеет значение в верхнем уровне файла, потому что выполнение или импортирование файла выполняют его утверждения сверху донизу, удостоверьтесь, что Вы помещаете невложенные вызовы функций или классов ниже определения функции или класса.
reload не воздействует на имена, скопированные from: reload работает намного лучше с утверждением import. Если Вы используете утверждение from, не забудьте повторно запускать from после reload, или Вы будете все еще иметь старые имена.
Порядок смешивания в многократном наследовании: так как суперклассы просматриваются слева направо, согласно порядку в линии заголовка класса, побеждают крайние левые, если то же самое имя появляется в несклольких суперклассах.
Пустой except в утверждениях try может захватить больше, чем Вы ожидаете. Except в try, которая не называет никакого исключения, захватывает каждое исключение - даже вещи как подлинные ошибки программирования и запрос sys.exit().
Кролики могут быть более опасными, чем они кажутся.

Источники:
python4u.ru

Комментарии: