Как найти ошибку в коде python

от admin

Мой код не работает 🙁

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

Если ничего из предложенного не работает, убедитесь, что вы редактируете тот же файл, который компилируете.


Картинка кликабельна

Программирование и мозг.

Одним из важнейших навыков, которым необходимо овладеть в программировании это умение отлаживать свои программы.

Программирование для меня в некотором роде странная вещь. Не скажу за всех программистов, но для себя я понял, что трачу огромную часть времени, пытаясь заставить программу работать. Я борюсь, напрягаюсь и провожу часы в глубоком разочаровании, пытаясь заставить свою программу работать корректно. Потом, когда понимаю, что добился своего и программа работает так как я хотел, наступает время эйфории. А секрет в том, что когда вы добиваетесь успеха, вы счастливы, ваш мозг высвобождает гормоны счастья — серотонин, дофамин, эндорфин и окситоцин, которые заставляют вас чувствовать себя счастливым. Оказывается, нашему мозгу все равно, успешно ли мы написали “Hello World”, или спарсили огромный конфиг с тысячи сетевых устройств (поверьте мне, это ужасно тяжело), мы все равно получим небольшую порцию гормонов, которые сделают нас счастливыми. Когда мы счастливы, мы хотим продолжать дальше и начинаем решать следующую маленькую проблему. И понимание процесса отладки как ни что иное может нам помочь в решении проблем в программировании, что в свою очередь запустит механизм получения гормонов счастья.

Что такое отладка

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

Обычно начинающие программисты винят во всех своих проблемах сам Python или другой язык программирования, знакомо да? И я не был исключением. Я ни в коем случае не считаю себя продвинутым, мне еще учиться и учиться, но после множества попыток ко мне пришло озарение(что-то типа Эврика, как у Архимеда или яблока у Ньютона). Я понял, что мне нужно научиться двум вещам для успешной работы моей программы — это, во-первых, понимать, что происходит, когда возникает та или иная ошибка и во-вторых овладеть основными методами их нахождения. Таким образом я осознал, что Python не является проблемой, проблема была в подходе к программированию на нем, который нужно было изменить. Поэтому я решил начать с того, какие основные сообщения об ошибках возникают, и понять почему они возникают.

Сообщения об ошибках

Многие проблемы в программе приведут к сообщению об ошибке, и, просто посмотрев на код сложно определить есть в нем какая-то ошибка или нет. К примеру, сможете ли вы увидеть, что не так в нижеприведенном коде, просто взглянув на код? Пример взят из книги Наташи Самойленко “Python для сетевых инженеров”

Возможно вы сразу увидите ошибку, а может и нет. Наш мозг склонен видеть то, что мы предполагаем увидеть. Это одно из когнитивных искажений, поэтому иногда очень трудно обнаружить проблему, просто взглянув на код. Особенно, когда это наш собственный код, и мы уверены, что сделали все правильно!

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

Теперь у нас есть сообщение об ошибке, которое может быть полезным. SyntaxError говорит нам, что у нас в коде недопустимый синтаксис. Также оно говорит нам, что ошибка находится на 16-й строке:

Это действительно полезная информация. Теперь вернемся к нашему коду и если внимательно посмотреть на 16-ую строку, то увидим, что с этой строкой все в порядке. Не кажется ли вам странным почему сообщение об ошибке указывает именно на эту строку? Мне тоже так казалось, пока я не стал изучать какие именно бывают сообщения об ошибках и почему они так работают в Python.

А начнем мы с одной из самых распространенных сообщений об ошибке ParseError.

ParseError

Сообщение об ошибке ParseError, по другому еще называется SyntaxError, происходит, когда вы делаете ошибку в синтаксисе кода. Синтаксические ошибки подобны грамматическим ошибкам. Если не использовать, скажем, в этой статье точки и запятые, то читателям будет сложно понять, что я пытаюсь описать. Аналогичным образом, на Python существуют определенные грамматические правила, которым необходимо следовать, иначе Python не сможет понять, что вы пытаетесь сказать.

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

Давайте еще раз вернемся к раннее приведенному коду:

С первого взгляда, тем более если вы только начинаете программировать, можно не заметить ошибки. Но взглянув на сообщение об ошибке, можно увидеть, что ошибка указана в 16-ой строке:

Хотя в этой строке не видно никакой ошибки:

Вместо этого, давайте вернемся к предыдущей, 15-ой строке:

Здесь мы видим, что скобка между запятой ‘,’и line2 не закрыта, а это синтаксическая ошибка. Так почему же Python показывает, что ошибка в 16-ой строке(см. Ошибка 1), а не 15-ой, где она явно присутствует? Для этого нужно понять способ работы Python со скобками. Python позволяет параметрам находиться в нескольких строках внутри скобок, он будет продолжать сканировать последующие строки в поисках закрывающей правой круглой скобки. В примере ниже мы видим, что можно переносить параметры в следующую строку. В нашем случае я перенес закрывающую скобку в 16- строку, и если запустить код, то все будет работать:

Однако в коде с ошибкой(см. Код 1), Python в 16-ой строке находит следующий line2 и хочет интерпретировать его как следующий параметр функции. Но, для того, чтобы отделить предыдущую(15-ую) строку от переменной line2 не хватает запятой. Поэтому, с точки зрения Python, ошибка — это пропущенная запятая. Ну а с нашей точки зрения — это пропущенная скобка.

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

Давайте рассмотрим тот же пример, но на этот раз представим, что в 15-ой строке у нас нет ошибки, мы ее нашли и закрыли скобку. Но из-за невнимательности не забыли закрыть скобку после print(vlans в 19- ой строке:

После запуска кода возникает следующая ошибка:

Здесь мы опять получили синтаксическую ошибку, но она будет как “unexpected EOF while parsing”. EOF означает End Of File, т.е. “конец файла”, а “parsing” — можно заменить на “чтение”. Поэтому “unexpected EOF while parsing” означает, что Python столкнулся с концом файла раньше, чем ожидал.

Способы обнаружения ошибок в коде

Так каким же способом можно найти эти проблемы в коде?

Один из способ, который может быть очень ценным в этой ситуации, это просто начать комментировать со строки, которая помечена в сообщении об ошибке. Давайте вернемся к первоначальному коду с ошибкой во 15-ой строке, где мы забыли закрыть скобку(см.Код ниже) и закомментируем строку, в которой указана ошибка(16 строка). В нашем случае это третья строка. Затем запустим скрипт:

Если мы закомментим 16-у строку, то сообщение об ошибке изменится на 17-ю.

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

В следующей статье мы рассмотрим следующие виды сообщений об ошибках:TypeError, ValueError и NameError.

Как найти ошибку в коде python

Until now error messages haven’t been more than mentioned, but if you have tried out the examples you have probably seen some. There are (at least) two distinguishable kinds of errors: syntax errors and exceptions.

8.1. Syntax Errors¶

Syntax errors, also known as parsing errors, are perhaps the most common kind of complaint you get while you are still learning Python:

The parser repeats the offending line and displays a little ‘arrow’ pointing at the earliest point in the line where the error was detected. The error is caused by (or at least detected at) the token preceding the arrow: in the example, the error is detected at the function print() , since a colon ( ‘:’ ) is missing before it. File name and line number are printed so you know where to look in case the input came from a script.

8.2. Exceptions¶

Even if a statement or expression is syntactically correct, it may cause an error when an attempt is made to execute it. Errors detected during execution are called exceptions and are not unconditionally fatal: you will soon learn how to handle them in Python programs. Most exceptions are not handled by programs, however, and result in error messages as shown here:

The last line of the error message indicates what happened. Exceptions come in different types, and the type is printed as part of the message: the types in the example are ZeroDivisionError , NameError and TypeError . The string printed as the exception type is the name of the built-in exception that occurred. This is true for all built-in exceptions, but need not be true for user-defined exceptions (although it is a useful convention). Standard exception names are built-in identifiers (not reserved keywords).

The rest of the line provides detail based on the type of exception and what caused it.

The preceding part of the error message shows the context where the exception occurred, in the form of a stack traceback. In general it contains a stack traceback listing source lines; however, it will not display lines read from standard input.

Built-in Exceptions lists the built-in exceptions and their meanings.

8.3. Handling Exceptions¶

It is possible to write programs that handle selected exceptions. Look at the following example, which asks the user for input until a valid integer has been entered, but allows the user to interrupt the program (using Control — C or whatever the operating system supports); note that a user-generated interruption is signalled by raising the KeyboardInterrupt exception.

The try statement works as follows.

First, the try clause (the statement(s) between the try and except keywords) is executed.

If no exception occurs, the except clause is skipped and execution of the try statement is finished.

If an exception occurs during execution of the try clause, the rest of the clause is skipped. Then, if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try/except block.

If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.

A try statement may have more than one except clause, to specify handlers for different exceptions. At most one handler will be executed. Handlers only handle exceptions that occur in the corresponding try clause, not in other handlers of the same try statement. An except clause may name multiple exceptions as a parenthesized tuple, for example:

A class in an except clause is compatible with an exception if it is the same class or a base class thereof (but not the other way around — an except clause listing a derived class is not compatible with a base class). For example, the following code will print B, C, D in that order:

Note that if the except clauses were reversed (with except B first), it would have printed B, B, B — the first matching except clause is triggered.

All exceptions inherit from BaseException , and so it can be used to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! It can also be used to print an error message and then re-raise the exception (allowing a caller to handle the exception as well):

Читать:
Как нарисовать окно программы

Alternatively the last except clause may omit the exception name(s), however the exception value must then be retrieved from sys.exc_info()[1] .

The try … except statement has an optional else clause, which, when present, must follow all except clauses. It is useful for code that must be executed if the try clause does not raise an exception. For example:

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.

When an exception occurs, it may have an associated value, also known as the exception’s argument. The presence and type of the argument depend on the exception type.

The except clause may specify a variable after the exception name. The variable is bound to an exception instance with the arguments stored in instance.args . For convenience, the exception instance defines __str__() so the arguments can be printed directly without having to reference .args . One may also instantiate an exception first before raising it and add any attributes to it as desired.

If an exception has arguments, they are printed as the last part (‘detail’) of the message for unhandled exceptions.

Exception handlers don’t just handle exceptions if they occur immediately in the try clause, but also if they occur inside functions that are called (even indirectly) in the try clause. For example:

8.4. Raising Exceptions¶

The raise statement allows the programmer to force a specified exception to occur. For example:

The sole argument to raise indicates the exception to be raised. This must be either an exception instance or an exception class (a class that derives from Exception ). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments:

If you need to determine whether an exception was raised but don’t intend to handle it, a simpler form of the raise statement allows you to re-raise the exception:

8.5. Exception Chaining¶

The raise statement allows an optional from which enables chaining exceptions. For example:

This can be useful when you are transforming exceptions. For example:

Exception chaining happens automatically when an exception is raised inside an except or finally section. This can be disabled by using from None idiom:

For more information about chaining mechanics, see Built-in Exceptions .

8.6. User-defined Exceptions¶

Programs may name their own exceptions by creating a new exception class (see Classes for more about Python classes). Exceptions should typically be derived from the Exception class, either directly or indirectly.

Exception classes can be defined which do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception.

Most exceptions are defined with names that end in “Error”, similar to the naming of the standard exceptions.

Many standard modules define their own exceptions to report errors that may occur in functions they define. More information on classes is presented in chapter Classes .

8.7. Defining Clean-up Actions¶

The try statement has another optional clause which is intended to define clean-up actions that must be executed under all circumstances. For example:

If a finally clause is present, the finally clause will execute as the last task before the try statement completes. The finally clause runs whether or not the try statement produces an exception. The following points discuss more complex cases when an exception occurs:

If an exception occurs during execution of the try clause, the exception may be handled by an except clause. If the exception is not handled by an except clause, the exception is re-raised after the finally clause has been executed.

An exception could occur during execution of an except or else clause. Again, the exception is re-raised after the finally clause has been executed.

If the finally clause executes a break , continue or return statement, exceptions are not re-raised.

If the try statement reaches a break , continue or return statement, the finally clause will execute just prior to the break , continue or return statement’s execution.

If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement.

A more complicated example:

As you can see, the finally clause is executed in any event. The TypeError raised by dividing two strings is not handled by the except clause and therefore re-raised after the finally clause has been executed.

In real world applications, the finally clause is useful for releasing external resources (such as files or network connections), regardless of whether the use of the resource was successful.

8.8. Predefined Clean-up Actions¶

Some objects define standard clean-up actions to be undertaken when the object is no longer needed, regardless of whether or not the operation using the object succeeded or failed. Look at the following example, which tries to open a file and print its contents to the screen.

The problem with this code is that it leaves the file open for an indeterminate amount of time after this part of the code has finished executing. This is not an issue in simple scripts, but can be a problem for larger applications. The with statement allows objects like files to be used in a way that ensures they are always cleaned up promptly and correctly.

After the statement is executed, the file f is always closed, even if a problem was encountered while processing the lines. Objects which, like files, provide predefined clean-up actions will indicate this in their documentation.

Отладка — Основы Python

Даже у самых опытных разработчиков код редко работает идеально с первого раза. Но чем выше навыки разработчика, тем лучше он отлаживает код, то есть анализирует ошибки и устраняет их.

Навык отладки сам по себе не появится: его необходимо развивать, причем начинать нужно как можно раньше. По ходу обучения вы будете выполнять задания и практиковаться — все это поможет набраться опыта. Со временем анализ и устранение ошибок войдут в привычку, если уделять достаточно внимания практике.

Как найти ошибку в коде

Можно отлаживать неисправный код методом тыка, но это долго и непродуктивно. Будет намного проще, если вы сначала поймете проблему, а уже потом начнете устранять ее. Понимание — это ключевой этап, без которого дальнейшие шаги невозможны.

Перед отладкой кода надо понять, что в нем не так. Это можно сделать за два шага.

Шаг 1. Изучить трейсбек (traceback) — список всех вызовов функций от запуска программы до места с ошибкой. Трейсбек помогает отследить, как прошло выполнение программы: какие функции у нее получилось вызвать успешно, а с какими — возникли сложности. Каждая запись в трейсбеке указывает на файл и строчку, а затем на выполняемую функцию.

Представим, что вы написали код в файле users.py и решили запустить функцию main() в четвертой строчке. Запись в трейсбеке будет выглядеть так:

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

Шаг 2. Когда трейсбек дойдет до проблемного места, он выдаст сообщение об ошибке. Например, такое:

Если владеете английским, то быстрее поймете, о чем идет речь в сообщении: «Название create не определено». Эта ошибка чаще всего происходит из-за опечатки в названии — нужно проверить этот момент. Без знания английского тоже можно разобраться, если обратиться к словарю или онлайн-переводчику.

Теперь посмотрим, как трейсбек и сообщение об ошибке выглядят вместе:

В примере выше видно всю цепочку событий: программа успешно справилась с функцией main() , а потом перешла к функции create() и столкнулась с ошибкой в названии.

Кроме NameError, в Python есть еще множество разных ошибок, которые можно разделить на три группы.

Типы ошибок

Самые простые и понятные ошибки — синтаксические. Они связаны исключительно с тем, что код неверно оформлен: например, использованы неправильные кавычки.

В выводе таких ошибок всегда присутствует фраза SyntaxError: . Чтобы отладить код в этом случае, нужно внимательно взглянуть на место с ошибкой. Посмотрим на примере. Здесь синтаксическая ошибка произошла потому, что использована кавычка ' вместо " :

Вторая большая группа ошибок — это ошибки программирования. Например, к ним относятся:

  • Вызов несуществующей функции
  • Использование необъявленной переменной
  • Передача неверных аргументов (например, аргументов неверного типа)

Эти ошибки исправить труднее, чем синтаксические. Обычно они возникают в результате неправильной логики в другом, более раннем вызове.

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

Способы отладки

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

Рассмотрим на конкретном примере. Ниже описана функция, которая считает сумму чисел от числа start до числа finish . Если start равно трем, а finish — пяти, то программа должна вычислить: 3 + 4 + 5 .

В этом коде допущена ошибка. Глядя на код функции sum_of_series() замечаем, что основных переменных там две: n и result . Из этого можно сделать такой вывод — нужно посмотреть, какие значения даются переменным на каждой итерации. После этого найти ошибку не составит труда.

Есть удобный способ отслеживать значения переменных во время выполнения кода — это визуальные отладчики. Они встраиваются в популярные редакторы кода и позволяют выполнить программу по шагам и наглядно увидеть все изменения. Если интересно узнать больше об отладчиках, можно поискать в Google по запросу Python Debuggers.

В среде Хекслета отладчика нет, поэтому здесь используется другой подход — отладочная печать. Суть такая же, как и в визуальном отладчике. Разница только в том, что для вывода значений переменных используется обычная печать на экран. То, что печатается на экран, отображается во вкладке OUTPUT , на которую автоматически переключается редактор во время проверки. Посмотрим на примере:

Здесь видно, что итераций цикла на одну меньше, чем нужно. Почему-то не выполняется сложение для последнего числа, которое обозначено как finish . И действительно, если посмотреть на определение, то видно, что там используется n < finish вместо n <= finish . Так с помощью отладки удалось найти ошибку — оказывается, был выбран знак < вместо <= .

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

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

Похожие статьи