Ошибка во время выполнения программы python

Обновлено: 07.07.2024

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

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

  1. Синтаксические.
  2. Логические (исключения).

Ошибки синтаксиса

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

Давайте посмотрим на один пример:

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

Мы можем заметить здесь, что в операторе if отсутствует двоеточие.

Логические ошибки (исключения)

Ошибки, возникающие во время выполнения (после прохождения проверки синтаксиса), называются исключениями или логическими ошибками.

Например, они возникают, когда мы пытаемся открыть файл (для чтения), который не существует (FileNotFoundError), пытаемся разделить число на ноль (ZeroDivisionError) или импортировать несуществующий модуль (ImportError).

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

Давайте посмотрим, как Python обрабатывает эти ошибки:

Встроенные исключения

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

Ниже перечислены некоторые из распространенных встроенных исключений в программировании на Python, а также ошибки, вызывающие их:

Я сделал игру с Python 2.6. Я также превратил его в исполняемый файл с помощью cx_Freeze 4.3. Когда я запустил исполняемый файл на моем компьютере, он получил это:

Я не понимаю, что здесь произошло. Я пытался решить проблему, но безуспешно. Затем я искал ошибку времени выполнения в Google, и он сказал, что это может быть совместимость. Я написал оригинальный скрипт для Windows Vista с загруженной версией Windows 7 Python 2.6. Я сделал это, потому что в Windows Vista не было интернета, поэтому я загрузил файлы python, pygame и cx_freeze .msi на свой ноутбук с Windows 7. Затем я перенес файлы на рабочий стол Windows Vista. Это проблема? Или, может быть, сценарий? Я не думаю, что это сценарий, так как я могу играть в игру, когда это все еще сценарий Python. Это немного долго, хотя. Я использую Windows 7 с Python 2.6, Pygame 2.6 и cx_freeze 4.3. Спасибо, если вы можете мне помочь:).

1 ответ

Ошибка времени выполнения - это тип ошибки, который преднамеренно возникает во время выполнения программы (отсюда и название). Например, в python ошибка времени выполнения может возникать только тогда, когда кто-то пишет

Это означает, что это способ остановить программу, если существует тип ошибки, который программный интерпретатор не обнаружил, но может заставить программу работать не так, как должна. Примером того, когда требуется ошибка времени выполнения, является эта функция (оболочка для функции colorcolor cprint, которая принимает число в качестве цветового аргумента):

В этом случае функция не будет работать должным образом или не сообщит пользователю о любой ошибке, если цветовой аргумент не равен 1 или 2. Способ решения этой проблемы и упрощение использования вашей программы - ошибка времени выполнения (или более крупные библиотеки будут создавать свои собственные исключения, это не важно). По сути, поскольку аргумент, отличный от 1 или 2, на самом деле является ошибкой в ​​вашей программе (этот питон не будет вас ловить и вызывать), он выдаст ошибку для диагностики пользователем. Вот новая и лучшая версия функции:

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

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

7.1.1. Известные ошибки в ПО¶

История знает множество примеров, где программные ошибки стоили не только огромных денег, но и человеческих жизней 6 7:

7.1.1.1. 1962 г.: ракета Маринер-1¶

Маринер-1 - космический аппарат США для изучения Венеры (Рисунок 7.1.1).

_images/07_01_01.jpg

Рисунок 7.1.1 - Ракета Маринер-1 9 ¶

Описание и причина:

Программист сделал ошибку, когда переводил рукописные математические формулы в код. Символ логического отрицания ¬ он принял за минус, и это привело к тому, что ракета воспринимала нормальные скорости как критические и из-за этого сбилась с курса.

Примерный ущерб / потери

Никто не погиб, однако экономические потери составили 18,3 млн. долларов.

7.1.1.2. 1985 г.: аппарат лучевой терапии Therac-25¶

Therac-25 - канадский аппарат лучевой терапии (Рисунок 7.1.2).

_images/07_01_02.jpg

Рисунок 7.1.2 - Аппарат лучевой терапии ` Therac-25 10 ¶

Описание и причина:

Неисправность была вызвана тем, что в проекте использовались библиотеки с ошибками, входящие в состав ПО аппарата Therac-20, что и привело к фатальным последствиям. В коде была найдена довольно распространенная ошибка многопоточности, называемое состоянием гонки. Тем не менее ошибку не заметили, так как Therac-20 работал исправно из-за дополнительных (аппаратных) мер предосторожности.

Примерный ущерб / потери

Умерло 2 человека, 4 получили серьезное облучение.

7.1.1.3. 1991 г.: ЗРК Patriot¶

Patriot - американский зенитный ракетный комплекс (Рисунок 7.1.3)

_images/07_01_03.jpg

Рисунок 7.1.3 - ЗРК Patriot 11 ¶

Описание и причина:

Во время Войны в Персидском заливе по казармам подразделений США был нанесен ракетный удар иракскими ракетами типа Р-17 (советская баллистическая ракета). Ни одна из ракет не была перехвачена, и удар достиг цели.

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

Проблема заключалась в том, что число \(\cfrac\) не имеет точного представления в двоичной системе счисления:

  • \(\cfrac_ = 0,0001100110011001100110011001100. _\) ;

  • \(\cfrac_ = 0,00011001100110011001100_\) (24-битное целое в системе Patriot);

  • ошибка 1 измерения:

Данной ошибки в измерениях было достаточно, чтобы ракета преодолела радиус поражения Patriot (Рисунок 7.1.4, Видео 7.1.1).

_images/07_01_04.jpg

Рисунок 7.1.4 - Неправильное определение зоны пролета ракеты 12 ¶

Видео 7.1.1 - Демонстрация ошибки ПО Patriot

Примерный ущерб / потери

Погибло 28 американских солдат и еще двести получили ранения.

7.1.1.4. 2000 г.: Проблема 2000 года (Y2K)¶

Описание и причина:

Разработчики программного обеспечения, выпущенного в XX веке, зачастую использовали два знака для представления года в датах: например, 1 января 1961 года представлялось как «01.01.61». При наступлении 1 января 2000 года при двузначном представлении года после 99 наступал 00 год (т.е. 99 + 1 = 00), что интерпретировалось многими старыми программами как 1900 год. Сложность была еще и в том, что многие программы обращались к вычислению дат вперед (например, при составлении плана закупок, планировании даты полета и т.д.) (Рисунок 7.1.5).

_images/07_01_05.jpg

Рисунок 7.1.5 - Табло показывает 3 января 1900 года, вместо 3 января 2000 года. Франция 13 ¶

Примерный ущерб / потери

30-300 млрд. долларов.

7.1.1.5. 2009-2011 г.: отзыв автомобилей Toyota¶

Описание и причина:

Неисправность в дроссельной заслонке (регулирует количество горючей смеси, поступающей в цилиндры двигателя внутреннего сгорания) с электронным контролем (ETC) приводила к случайным ускорениям автомобиля (Видео 7.1.2).

Видео 7.1.2 - Случайное ускорение автомобиля

В ходе десятимесячного расследования специалисты NASA выявили, что программное обеспечение не соответствует стандартам MISRA (англ. Motor Industry Software Reliability Association) и содержит 7134 нарушения. Представители Toyota ответили, что у них свои собственные стандарты.

20 декабря 2010 года Тойота отвергнула обвинения, но выплатила 16 млрд. долларов в досудебном порядке по искам, выпустила обновление ПО для некоторых моделей машин и отозвала 5,5 млн. автомобилей 8 (Рисунок 7.1.6).

_images/07_01_06.jpg

Рисунок 7.1.6 - Lexus ES 350 2007-2010 - одна из моделей с неисправностью 14 ¶

Примерный ущерб / потери

Погибло не менее 89 человек, многомиллиардные потери компании.

7.1.2. Определение и разновидности ошибок¶

Ошибка (также баг от англ. Software Bug) - неполадка в программе, из-за которой она ведет себя неопределенно, выдавая неожиданный результат.

Основные категории ошибок:

ошибки времени выполнения;

7.1.2.1. Синтаксические ошибки¶

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

Python — это интерпретируемый, объектно-ориентированный язык программирования высокого уровня с динамической семантикой. Встроенные структуры данных высокого уровня в сочетании с динамической типизацией и динамическим связыванием делают его очень привлекательным для БРПС (быстрой разработки прикладных средств), а также для использования в качестве скриптового и связующего языка для подключения существующих компонентов или сервисов. Python поддерживает модули и пакеты, тем самым поощряя модульность программы и повторное использование кода.

О данной статье

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

Имея это в виду, в этой статье представлен «топ-10» тонких, трудных для обнаружения ошибок, которые могут допустить даже продвинутые разработчики Python.

Ошибка № 1: неправильное использование выражений в качестве значений по умолчанию для аргументов функций

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


Распространенная ошибка в данном случае — это думать, что значение необязательного аргумента будет устанавливаться в значение по умолчанию каждый раз, как функция будет вызываться без значения для этого аргумента. В приведенном выше коде, например, можно предположить, что повторно вызывая функцию foo() (то есть без указания значения для агрумента bar), она всегда будет возвращать «baz», поскольку предполагается, что каждый раз, когда вызывается foo () (без указания аргумента bar), bar устанавливается в [ ] (т. е. новый пустой список).

Но давайте посмотрим что же будет происходить на самом деле:


А? Почему функция продолжает добавлять значение по умолчанию «baz» к существующему списку каждый раз, когда вызывается foo(), вместо того, чтобы каждый раз создавать новый список?

Ответом на данный вопрос будет более глубокое понимание того, что творится у Python «под капотом». А именно: значение по умолчанию для функции инициализируется только один раз, во время определения функции. Таким образом, аргумент bar инициализируется по умолчанию (т. е. пустым списком) только тогда, когда foo() определен впервые, но последующие вызовы foo() (т. е. без указания аргумента bar) продолжат использовать тот же список, который был создан для аргумента bar в момент первого определения функции.

Для справки, распространенным «обходным путем» для этой ошибки является следующее определение:

Ошибка № 2: неправильное использование переменных класса

Рассмотрим следующий пример:


Вроде все в порядке.


Ага, все как и ожидалось.


Что за черт?! Мы же только изменили A.x. Почему же C.x тоже изменилось?

В Python переменные класса обрабатываются как словари и следуют тому, что часто называют Порядком разрешения методов (MRO). Таким образом, в приведенном выше коде, поскольку атрибут x не найден в классе C, он будет найден в его базовых классах (только A в приведенном выше примере, хотя Python поддерживает множественное наследование). Другими словами, C не имеет своего собственного свойства x, независимого от A. Таким образом, ссылки на C.x фактически являются ссылками на A.x. Это будет вызывать проблемы, если не обрабатывать такие случаи должным образом. Так что при изучении Python обратите особое внимание на аттрибуты класса и работу с ними.

Ошибка № 3: неправильное указание параметров для блока исключения

Предположим, что у вас есть следующий кусок кода:


Проблема здесь заключается в том, что выражение except не принимает список исключений, указанных таким образом. Скорее, в Python 2.x выражение «except Exception, e» используется для привязки исключения к необязательному второму заданному второму параметру (в данном случае e), чтобы сделать его доступным для дальнейшей проверки. В результате в приведенном выше коде исключение IndexError не перехватывается выражением except; скорее, вместо этого исключение заканчивается привязкой к параметру с именем IndexError.

Правильный способ перехвата нескольких исключений с помощью выражения except — указать первый параметр в виде кортежа, содержащего все исключения, которые нужно перехватить. Кроме того, для максимальной совместимости используйте ключевое слово as, так как этот синтаксис поддерживается как в Python 2, так и в Python 3:

Ошибка № 4: непонимание правил области видимости Python

Область видимости в Python основана на так называемом правиле LEGB, которое является аббревиатурой Local (имена, назначенные любым способом внутри функции (def или lambda), и не объявленные глобальными в этой функции), Enclosing (имя в локальной области действия любых статически включающих функций (def или lambda), от внутреннего к внешнему), Global (имена, назначенные на верхнем уровне файла модуля, или путем выполнения global инструкции в def внутри файла), Built-in (имена, предварительно назначенные в модуле встроенных имен: open, range, SyntaxError . ). Кажется достаточно просто, верно? Ну, на самом деле, есть некоторые тонкости в том, как это работает в Python, что подводит нас к общей более сложной проблеме программирования на Python ниже. Рассмотрим следующей пример:

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

Таким образом, многие удивляются, когда получают UnboundLocalError в ранее работающем коде, когда он модифицируется путем добавления оператора присваивания где-нибудь в теле функции.

Эта особенность особенно сбивает разработчиков с толку при использовании списков. Рассмотрим следующий пример:


А? Почему foo2 падает, в то время как foo1 работает нормально?

Ответ такой же, как в предыдущем примере, но, по распространенному мнению, здесь ситуация более тонкая. foo1 не применяет оператор присваивания к lst, тогда как foo2 — да. Помня, что lst + = [5] на самом деле является просто сокращением для lst = lst + [5], мы видим, что мы пытаемся присвоить значение lst (поэтому Python предполагает, что он находится в локальной области видимости). Однако значение, которое мы хотим присвоить lst, основано на самом lst (опять же, теперь предполагается, что он находится в локальной области видимости), который еще не был определен. И мы получаем ошибку.

Ошибка № 5: изменение списка во время итерации по нему

Проблема в следующем куске кода должна быть достаточно очевидной:


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

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

Ошибка № 6: непонимание того, как Python связывает переменные в замыканиях

Рассмотрим следующий пример:


Вы можете ожидать следующий вывод:


Но на самом деле вы получите вот что:

Это происходит из-за поздней привязки в Python, которое заключается в том, что значения переменных, используемых в замыканиях, ищутся во время вызова внутренней функции. Таким образом, в приведенном выше коде всякий раз, когда вызывается какая-либо из возвращаемых функций, значение i ищется в окружающей области видимости во время ее вызова (а к тому времени цикл уже завершился, поэтому i уже был присвоен конечный результат — значение 4).

Решение этой распространенной проблемы с Python будет таким:


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

Ошибка № 7: создание циклических зависимостей модуля

Допустим, у вас есть два файла, a.py и b.py, каждый из которых импортирует другой, следующим образом:


Сначала попробуем импортировать a.py:


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

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

Итак, возвращаясь к нашему примеру, когда мы импортировали a.py, у него не было проблем с импортом b.py, поскольку b.py не требует, чтобы что-либо из a.py было определено во время его импорта. Единственная ссылка в b.py на a — это вызов a.f(). Но этот вызов в g() и ничего в a.py или b.py не вызывает g(). Так что все работает прекрасно.

Но что произойдет, если мы попытаемся импортировать b.py (без предварительного импорта a.py, то есть):


Ой-ой. Это не хорошо! Проблема здесь в том, что в процессе импорта b.py он пытается импортировать a.py, который, в свою очередь, вызывает f(), который пытается получить доступ к b.x. Но b.x еще не было определено. Отсюда исключение AttributeError.

По крайней мере, одно из решений этой проблемы довольно тривиально. Просто измените b.py, чтобы импортировать a.py в g():


Теперь, когда мы его импортируем, все нормально:

Ошибка № 8: пересечение имен с именами модулями стандартной библиотеки Python

Одна из прелестей Python — это множество модулей, которые поставляются «из коробки». Но в результате, если вы сознательно не будете за этим следить, можно столкнуться с тем, что имя вашего модуля может быть с тем же именем, что и модуль в стандартной библиотеке, поставляемой с Python (например, в вашем коде может быть модуль с именем email.py, который будет конфликтовать со модулем стандартной библиотеки с таким же именем).

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

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

Ошибка № 9: неспособность учесть различия Python 2 и Python 3

Рассмотрим следующий файл foo.py:


На Python 2 он отработает нормально:


Но теперь давайте посмотрим как он будет работать в Python 3:


Что здесь только что произошло? «Проблема» в том, что в Python 3 объект в блоке исключения недоступен за его пределами. (Причина этого заключается в том, что в противном случае объекты в этом блоке будут сохраняться в памяти до тех пор, пока сборщик мусора не запустится и не удалит ссылки на них оттуда).

Один из способов избежать этой проблемы — сохранить ссылку на объект блока исключения за пределами этого блока, чтобы он оставался доступным. Вот версия предыдущего примера, которая использует эту технику, тем самым получая код, который подходит как для Python 2, так и для Python 3:


Запустим его в Python 3:

Ошибка № 10: неправильное использование метода __del__

Допустим, у вас есть вот такой файл mod.py:


И вы пытаетесь сделать вот такое из другого another_mod.py:


И получите ужасный AttributeError.

Почему? Потому что, как сообщается здесь, когда интерпретатор отключается, глобальные переменные модуля все имеют значение None. В результате в приведенном выше примере, в момент вызова __del__, имя foo уже было установлено в None.

Решением этой «задачи со звездочкой» будет использование atexit.register(). Таким образом, когда ваша программа завершает выполнение (то есть при нормальном выходе из нее), ваши handle'ы удаляются до того, как интерпретатор звершает работу.

С учетом этого, исправление для приведенного выше кода mod.py может выглядеть примерно так:


Подобная реализация обеспечивает простой и надежный способ вызова любой необходимой очистки после обычного завершения программы. Очевидно, что решение о том, как поступить с объектом, который связан с имненем self.myhandle, остается за foo.cleanup, но, думаю, идею вы поняли.

Заключение

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

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

Читайте также: