Содержание

Профессионально обрабатываем исключения в Python / Хабр

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

Эффективная обработка исключений

Введение

Давайте рассмотрим следующую систему. У нас есть микросервис, который отвечает за:

·  Прослушивание событий о новом заказе;

·  Получение заказа из базы данных;

·  Проверку состояния принтера;

·  Печать квитанции;

·  Отправка квитанции в налоговую систему (IRS).

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

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

И примерно вот такой код на этот случай пишут люди (он, конечно, работает, но плохо и неэффективно):

class OrderService:
    def emit(self, order_id: str) -> dict:
        try:
            order_status = status_service.get_order_status(order_id)
        except Exception as e:
            logger.exception(
                f"Order {order_id} was not found in db "
                f"to emit. Error: {e}."
            )
            raise e
        (
            is_order_locked_in_emission,
            seconds_in_emission,
        ) = status_service.is_order_locked_in_emission(order_id)
        if is_order_locked_in_emission:
            logger.info(
                "Redoing emission because "
                "it was locked in that state after a long time! "
                f"Time spent in that state: {seconds_in_emission} seconds "
                f"Order: {order_id}, "
                f"order_status: {order_status.
value}" ) elif order_status == OrderStatus.EMISSION_IN_PROGRESS: logger.info("Aborting emission request because it is already in progress!") return {"order_id": order_id, "order_status": order_status.value} elif order_status == OrderStatus.EMISSION_SUCCESSFUL: logger.info( "Aborting emission because it already happened! " f"Order: {order_id}, " f"order_status: {order_status.value}" ) return {"order_id": order_id, "order_status": order_status.value} try: receipt_note = receipt_service.create(order_id) except Exception as e: logger.exception( "Error found during emission! " f"Order: {order_id}, " f"exception: {e}" ) raise e try: broker.emit_receipt_note(receipt_note) except Exception as e: logger.exception( "Emission failed! " f"Order: {order_id}, " f"exception: {e}" ) raise e order_status = status_service.
get_order_status(order_id) return {"order_id": order_id, "order_status": order_status.value}

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

Почему этот сервис — blob?

Этот сервис знает слишком много. Кто-то может сказать, что он знает только то, что ему нужно (то есть все шаги, связанные с формированием чека), но на самом деле он знает куда больше.

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

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

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

Первое улучшение: делайте исключения конкретными

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

Я выделю только то, что мы поменяли:

try:
    order_status = status_service.get_order_status(order_id)
except Exception as e:
    logger.exception(...)
    raise OrderNotFound(order_id) from e
...
try:
    ...
except Exception as e:
    logger.exception(...)
    raise ReceiptGenerationFailed(order_id) from e
try:
    broker.emit_receipt_note(receipt_note)
except Exception as e:
    logger.exception(...)
    raise ReceiptEmissionFailed(order_id) from e

Обратите внимание, что на этот раз я пользуюсь from e, что является правильным способом создания одного исключения из другого и сохраняет полную трассировку стека.

Второе улучшение: не лезьте не в свое дело

Теперь, когда у нас есть кастомные исключения, мы можем перейти к мысли «не учите классы тому, что может пойти не так» — они сами скажут, если это случится!

# Services
class StatusService:
    def get_order_status(order_id):
        try:
            ...
        except Exception as e:
            raise OrderNotFound(order_id) from e
class ReceiptService:
    def create(order_id):
        try:
            ...
        except Exception as e:
            raise ReceiptGenerationFailed(order_id) from e
class Broker:
    def emit_receipt_note(receipt_note):
        try:
            ...
        except Exception as e:
            raise ReceiptEmissionFailed(order_id) from e
# Main class
class OrderService:
    def emit(self, order_id: str) -> dict:
        try:
            order_status = status_service.get_order_status(order_id)
            (
                is_order_locked_in_emission,
                seconds_in_emission,
            ) = status_service.
is_order_locked_in_emission(order_id) if is_order_locked_in_emission: logger.info( "Redoing emission because " "it was locked in that state after a long time! " f"Time spent in that state: {seconds_in_emission} seconds " f"Order: {order_id}, " f"order_status: {order_status.value}" ) elif order_status == OrderStatus.EMISSION_IN_PROGRESS: logger.info("Aborting emission request because it is already in progress!") return {"order_id": order_id, "order_status": order_status.value} elif order_status == OrderStatus.EMISSION_SUCCESSFUL: logger.info( "Aborting emission because it already happened! " f"Order: {order_id}, " f"order_status: {order_status.value}" ) return {"order_id": order_id, "order_status": order_status.
value} receipt_note = receipt_service.create(order_id) broker.emit_receipt_note(receipt_note) order_status = status_service.get_order_status(order_id) except OrderNotFound as e: logger.exception( f"Order {order_id} was not found in db " f"to emit. Error: {e}." ) raise except ReceiptGenerationFailed as e: logger.exception( "Error found during emission! " f"Order: {order_id}, " f"exception: {e}" ) raise except ReceiptEmissionFailed as e: logger.exception( "Emission failed! " f"Order: {order_id}, " f"exception: {e}" ) raise else: return {"order_id": order_id, "order_status": order_status.value}

Как вам? Намного лучше, правда? У нас есть один блок try, который построен достаточно логично, чтобы понять, что произойдет дальше. Вы сгруппировали конкретные блоки, за исключением тех, которые помогают вам понять «когда» и крайние случаи. И, наконец, у вас есть блок else, в котором описано, что произойдет, если все отработает как надо.

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

Но это еще не все. Логирование продолжает меня раздражать.

Третье улучшение: улучшение логирования

Этот шаг напоминает мне принцип «говори, а не спрашивай», хотя это все же не совсем он. Вместо того, чтобы запрашивать подробности исключения и на их основе выдавать полезные сообщения, исключения должны выдавать их сами – в конце концов, я их конкретизировал!

### Exceptions
class OrderCreationException(Exception):
    pass
class OrderNotFound(OrderCreationException):
    def __init__(self, order_id):
        self. order_id = order_id
        super().__init__(
            f"Order {order_id} was not found in db "
            "to emit."
        )
class ReceiptGenerationFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id
        super().__init__(
            "Error found during emission! "
            f"Order: {order_id}"
        )
class ReceiptEmissionFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id
        super().__init__(
            "Emission failed! "
            f"Order: {order_id} "
        )
### Main class
class OrderService:
    def emit(self, order_id: str) -> dict:
        try:
            ...
        except OrderNotFound:
            logger.exception("We got a database exception")
            raise
        except ReceiptGenerationFailed:
            logger.exception("We got a problem generating the receipt")
            raise
        except ReceiptEmissionFailed:
            logger.exception("Unable to emit the receipt")
            raise
        else:
            return {"order_id": order_id, "order_status": order_status. value}

Наконец-то мои глаза чувствуют облегчение. Поменьше повторений, пожалуйста! Примите к сведению, что рекомендуемый способ выглядит так, как я написал его выше: logger.exception(«ЛЮБОЕ СООБЩЕНИЕ»). Вам даже не нужно передавать исключение, поскольку его наличие уже подразумевается. Кроме того, кастомное сообщение, которое мы определили в каждом исключении с идентификатором order_id, будет отображаться в логах, поэтому вам не нужно повторяться и не нужно оперировать внутренними данными об исключениях.

Вот пример вывода ваших логов:

❯ python3 testme.py
Unable to emit the receipt # <<-- My log message
Traceback (most recent call last):
  File "/path/testme.py", line 19, in <module>
    tryme()
  File "/path/testme.py", line 14, in tryme
    raise ReceiptEmissionFailed(order_id)
ReceiptEmissionFailed: Emission failed! Order: 10 # <<-- My exception message

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

Последнее улучшение: упрощение

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

Но управляет ли OrderService бизнес-логикой? Я не думаю, что это сервис в общем смысле. Он больше похож на координацию вызовов настоящих сервисов обеспечивающих бизнес-логику, которая выглядит получше, чем паттерн facade.

Кроме того, можно заметить, что он запрашивает данные у status_service, чтобы что-то с ними сделать. (Что, на этот раз, действительно разрушает идею «Говори, а не спрашивай»).

Перейдем к упрощению.

class OrderFacade:  # Renamed to match what it actually is
    def emit(self, order_id: str) -> dict:
        try:
            # NOTE: info logging still happens inside
            status_service.ensure_order_unlocked(order_id)
            receipt_note = receipt_service.create(order_id)
            broker.emit_receipt_note(receipt_note)
            order_status = status_service. get_order_status(order_id)
        except OrderAlreadyInProgress as e:
            # New block
            logger.info("Aborting emission request because it is already in progress!")
            return {"order_id": order_id, "order_status": e.order_status.value}
        except OrderAlreadyEmitted as e:
            # New block
            logger.info(f"Aborting emission because it already happened! {e}")
            return {"order_id": order_id, "order_status": e.order_status.value}
        except OrderNotFound:
            logger.exception("We got a database exception")
            raise
        except ReceiptGenerationFailed:
            logger.exception("We got a problem generating the receipt")
            raise
        except ReceiptEmissionFailed:
            logger.exception("Unable to emit the receipt")
            raise
        else:
            return {"order_id": order_id, "order_status": order_status.value}

Мы только что создали новый метод ensure_order_unlocked для нашего status_service, который теперь отвечает за создание исключений/логирование в случае, если что-то идет не так.

Хорошо, а теперь скажите, насколько легче теперь стало это читать?

Я могу понять все return при беглом просмотре. Я знаю, что происходит, когда все идет хорошо, и как крайние случаи могут привести к разным результатам. И все это без прокрутки взад-вперед.

Теперь этот код такой же простой, каким (в основном) должен быть любой код.

Обратите внимание, что я решил вывести объект исключения e в логах, поскольку под капотом он будет запускать str(e), который вернет сообщение об исключении. Я подумал, что было бы полезно говорить подробно, поскольку мы не используем log.exception для этого блока, поэтому сообщение об исключении не будет отображаться.

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

Эффективное создание исключений

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

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

# Base category exception
class OrderCreationException(Exception):
    pass
# Specific error with custom message. Order id is required.
class OrderNotFound(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id  # custom property
        super().__init__(
            f"Order {order_id} was not found in db "
            f"to emit."
        )
# Specific error with custom message. Order id is required.
class ReceiptGenerationFailed(OrderCreationException):
    def __init__(self, order_id):
        self.order_id = order_id  # custom property
        super(). __init__(
            "Error found during emission! "
            f"Order: {order_id}"
        )

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

def func1(order_id):
    raise OrderNotFound(order_id)
    # instead of raise OrderNotFound(f"Can't find order {order_id}")
def func2(order_id):
    raise OrderNotFound(order_id)
    # instead of raise OrderNotFound(f"Can't find order {order_id}")

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

assert e.order_id == order_id
# instead of assert order_id in str(e)

Ловим и создаем исключения эффективно

Еще одна вещь, которую люди часто делают неправильно – это отлавливают и повторно создают исключения.

Согласно PEP 3134 Python, делать нужно следующим образом.

Повторное создание исключения

Обычной инструкции raise более чем достаточно.

try:
    ...
except CustomException as ex:
    # do stuff (e.g. logging)
    raise

Создание одного исключения из другого

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

try:
    ...
except CustomException as ex:
    raise MyNewException() from ex

Эффективное логирование исключений

Еще один совет, который не позволит вам быть слишком многословным.

Используйте logger.exception

Вам не нужно логировать объект исключения. Функция exception логгера предназначена для использования внутри блоков except. Она уже обрабатывает трассировку стека с информацией о выполнении и отображает, какое исключение вызвало ее, с сообщением, установленном на уровне ошибки!

try:
    . ..
except CustomException:
    logger.exception("custom message")

А что, если это не ошибка?

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

Вы можете принять решение установить exc_info в True, если хотите сохранить трассировку стека. Кроме того, было бы неплохо использовать объект исключения внутри сообщения.

Источники

Документация Python:

·  Python logging.logger.exception

·  Python PEP 3134

Принципы и качество кода:

·  Говори, а не спрашивай

·  Паттерн facade

·  Blob


Материал подготовлен в рамках курса «Python Developer. Professional».

Всех желающих приглашаем на онлайн-интенсив «Быстрая разработка JSON API приложений на Flask». На этом интенсиве мы:
— Познакомимся со спецификацией JSON API;
— Узнаем, что такое сериализация/десериализация данных;
— Узнаем, что такое marshmallow и marshmallow-jsonapi;
— Познакомимся со Swagger;
— Посмотрим на обработку и выдачу связей.


РЕГИСТРАЦИЯ

Обработка/перехват исключений try/except в Python.

Можно писать программы, которые обрабатывают выбранные исключения. Посмотрите на следующий пример, который запрашивает ввод у пользователя до тех пор, пока не будет введено правильное целое число, но позволяет пользователю прерывать программу, используя Ctrl-C или что-либо поддерживаемое операционной системой. Обратите внимание, что сгенерированное пользователем прерывание сигнализируется возбуждением исключения KeyboardInterrupt.

while True:
     try:
         x = int(input("Пожалуйста, введите целое число: "))
         break
     except ValueError:
         print("Это не целое число. Попробуйте снова...")

Оператор try/except работает следующим образом:

  • Сначала выполняется инструкция try — код между ключевыми словами try и except.
  • Если исключение не возникает, инструкция except пропускается и выполнение оператора try завершается.
  • Если во время выполнения кода в инструкции try возникает исключение, остальная часть кода этого блока пропускается. Затем, если тип исключения соответствует исключению, записанному после ключевого слова except, блок кода этой инструкции except выполняется, а затем выполнение программы продолжается после всей конструкции try.
  • Если возникает исключение, которое не соответствует исключению, записанному в инструкции except, оно передается внешним операторам try. Если обработчик не найден, то это считается необработанным исключением, следовательно выполнение останавливается с сообщением об ошибке выполнения.

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

except (RuntimeError, TypeError, NameError):
    pass

Класс в инструкции except совместим с исключением, если это тот же самый класс или его базовый класс, но не наоборот. Инструкция except, перечисляющая производный класс, не совместима с базовым классом. Например, следующий код будет печатать B, C, D в таком порядке:

class B(Exception):
    pass
class C(B):
    pass
class D(C):
    pass
for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print('D')
    except C:
        print('C')
    except B:
        print('B')

Обратите внимание, что если бы инструкции except были отменены (с первым исключением B), то вывелось бы B, B, B — срабатывает первое совпадающее предложение except.

Последняя инструкция except может опустить имя исключения. Используйте это с крайней осторожностью, таким образом можно легко замаскировать реальную ошибку программирования! Здесь можно использовать sys.exc_info() или модуль traceback для вывода сообщения об ошибке или ее сохранения для дельнейшего анализа, а затем повторно вызвать исключение при помощи оператора raise, что позволяет вызывающей стороне также обработать исключение:

import traceback
try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Не удалось преобразовать данные в целое число.")
except:
    print("Непредвиденная ошибка...")
    # сохраняем исключение для дальнейшего анализа.
    with open('trace.txt', 'a') as fp:
        traceback.print_exc(file=fp)
    # повторный вызов исключения, если это необходимо.
    raise

В примере выше, сведения об ошибке сохраняются в файл trace.txt с использованием встроенной функции open(). Лучшей практикой сохранения исключений для дальнейшего анализа в подобных ситуациях является применение модуля logging.

Конструкция try/except может содержать необязательную инструкцию else, которая при наличии должна следовать за всеми инструкциями except. Блок кода в инструкции else будет выполнен в том случае, если код в инструкции try не вызывает исключения. Например:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('не удается открыть', arg)
    else:
        print(arg, 'имеет', len(f.readlines()), 'строк')
        f.close()

Использование инструкции else лучше, чем добавление дополнительного кода в инструкцию try, поскольку позволяет избежать случайного перехвата исключения, которое не было вызвано кодом, защищенным конструкцией try/except.

Когда возникает исключение, оно может иметь связанное значение, также известное как аргумент исключения. Наличие и тип аргумента зависят от типа исключения.

Инструкция except может указывать переменную после имени исключения. Переменная привязывается к экземпляру исключения с аргументами, хранящимися в экземпляре instance.args. Для удобства, экземпляр исключения определяет __str__(), так что аргументы могут быть напечатаны непосредственно без необходимости ссылаться instance.args. Кроме того, можно создать экземпляр исключения прежде, чем вызвать его и добавить любые атрибуты к нему по желанию.

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    # экземпляр исключения
    print(type(inst))
    # аргументы, хранящиеся внутри
    print(inst.args)
    # __str__ позволяет печатать args напрямую, но может
    # быть переопределен в подклассах исключений
    print(inst)
    # распаковка аргументов
    x, y = inst.args
    print('x =', x)
    print('y =', y)
# <class 'Exception'>
# ('spam', 'eggs')
# ('spam', 'eggs')
# x = spam
# y = eggs

Если исключение имеет аргументы, они печатаются как последняя часть («деталь») сообщения для необработанных исключений.

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

def this_fails():
    x = 1/0
try:
    this_fails()
except ZeroDivisionError as err:
    print('Handling run-time error:', err)
#Handling run-time error: division by zero

Тонкости работы конструкции try/except/else/finally в Python.

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

В конструкция try/except/else/finally определяется обработчики исключений и/или код очистки для группы операторов.

Поведение инструкции

except.

В инструкциях except указывается один или несколько обработчиков исключений. Если в коде между инструкциями try/except не возникает исключения, то обработчик исключений не выполняется. При возникновении исключения в коде между инструкциями try/except запускается поиск обработчика исключения. Этот поиск проверяет выражения во всех инструкциях except по очереди, пока не будет найден тот, который соответствует исключению.

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

import sys
try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err: # содержит выражение OSError as err
    print("OS error: {0}".format(err))
except ValueError: # содержит выражение ValueError
    print("Не удалось преобразовать данные в целое число.")
except: # инструкция 'except' без выражения
    print("Непредвиденная ошибка:", sys.exc_info()[0])

Для инструкций except с выражением — выражение оценивается и сравнивается с брошенным исключением в коде, между инструкцией try и первым except. Выражение соответствует исключению в том случае, если результирующий объект выражения «совместим» с исключением. Объект выражения совместим с исключением, если это класс или базовый класс объекта исключения или кортеж, содержащий элемент, совместимый с исключением.

try:
...
except (OSError, ValueError):
# инструкция 'except' содержит 
# кортеж возможных исключений
...

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

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

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

Когда исключение было назначено с помощью выражения as target, то переменная target очищается в конце предложения except. Это как если бы:

except E as N:
    foo

был переведен на:

except E as N:
    try:
        foo
    finally:
        del N

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

Перед выполнением блока кода инструкции except, сведения об исключении сохраняются в модуле sys и могут быть доступны через sys.exc_info(). Метод sys.exc_info() возвращает тройной кортеж, состоящий из класса исключения, экземпляра исключения и объекта трассировки, идентифицирующего точку в программе, где произошло исключение. Значение метода sys.exc_info() восстанавливаются до своих предыдущих значений (до вызова) при возврате из функции, которая обработала исключение.

Поведение необязательной инструкции

else.

Необязательная инструкция else выполняется, только если поток управления покидает блок кода между try/except, не вызвав исключения и если не было выполнено ни одного оператора return, continue или break в этом блоке кода.

Исключения в блоке кода else не обрабатываются предыдущими инструкциями except.

Поведение необязательной инструкции

finally.

Если в конструкции try присутствует инструкция finally, то она определяет обработчик 'cleanup'. Оператор try выполняет все инструкции, включая except и else. Если исключение возникает в любой из этих инструкции и не обрабатывается, то исключение временно сохраняется. При исполнении инструкции finally, если существует сохраненное исключение, оно повторно вызывается после выполнения блока кода инструкции finally. Если finally вызывает другое исключение, то сохраненное исключение устанавливается как контекст нового исключения. Если инструкция finally выполняет оператор return, break или continue, то сохраненное исключение отбрасывается:

def f():
    try:
        1/0
    finally:
        return 42
>>> f()
# 42

Информация об исключении недоступна программе во время выполнения инструкции finally.

Когда операторы return, break или continue выполняется в блоке кода инструкции try конструкции try/finally, блок кода инструкции finally также выполняется ‘на выходе.’

Возвращаемое значение функции определяется последним выполненным оператором return. Поскольку инструкция finally выполняется в любом случае, оператор return, выполняемый в блоке кода инструкции finally, всегда будет последним выполненным:

def foo():
    try:
        return 'try'
    finally:
        return 'finally'
>>> foo()
# 'finally'

Дополнительную информацию об исключениях можно найти в материалах «Ошибки выполнения программы» и «Обработка исключений в Python», а информацию об использовании инструкции raise генерации исключений можно найти в материале «Инструкция raise в Python»

Python Try Except — GeeksforGeeks

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

  • IOError: , если файл не может быть открыт
  • KeyboardInterrupt: , когда пользователь нажимает ненужную клавишу
  • ValueError: , когда встроенная функция получает неправильный аргумент
  • Error5
    EOF04 End-Of-File срабатывает без чтения каких-либо данных
  • ImportError: если не удается найти модуль Питон. Блок try используется для проверки некоторого кода на наличие ошибок, т. е. код внутри блока try будет выполняться, когда в программе нет ошибок. Принимая во внимание, что код внутри блока exclude будет выполняться всякий раз, когда программа сталкивается с какой-либо ошибкой в ​​предыдущем блоке try.
     

    Синтаксис:  

     попытка:
        # Некоторый код
    кроме:
        # Выполняется, если ошибка в
        # блок try 

    Как работает try()?  
     

    • Сначала выполняется предложение try , то есть код между try и , кроме предложения .
    • Если исключения нет, то будет выполняться только предложение try , кроме , предложение завершено.
    • Если возникает какое-либо исключение, 9Пункт 0004 try будет пропущен, а пункт , кроме , будет запущен.
    • Если возникает какое-либо исключение, но предложение , кроме в коде, не обрабатывает его, оно передается внешним операторам try . Если исключение остается необработанным, выполнение останавливается.
    • Оператор try может иметь более одного , кроме пункта .
       

      Python3

      def divide(x, y):

           try :

              

               result = x / / Y

      Печать ( "Да! Ваш ответ:" , результат)

      999 except ZeroDivisionError:

               print ( "Sorry ! You are dividing by zero " )

       

      divide( 3 , 2 )

      Вспомогательное пространство: O (1)

      Выход:

       ('Да! Ваш ответ:', 1) 

      Код 1: есть. только , за исключением пункта .
       

      Python3

      def divide(x, y):

           try :

              

               result = x / / y

               print ( "Да! Ваш ответ :" , результат)0100

           except ZeroDivisionError:

               print ( "Sorry ! You are dividing by zero " )

       

      divide( 3 , 0 )

      Вывод :

       Извините ! Вы делите на ноль 

       

      Еще Пункт

      В python вы также можете использовать предложение else в блоке try-except, которое должно присутствовать после всех предложений кроме. Код входит в блок else только в том случае, если предложение try не вызывает исключения.
       

      Синтаксис:

       попытка:
          # Некоторый код
      кроме:
          # Выполняется, если ошибка в
          # пробный блок
      еще:
          # выполнить, если нет исключений 

      Код:

      Python3

         

      def AbyB(a , b):

           try :

               c = ((a + b) / / (a - b))

           except ZeroDivisionError:

               print ( "a/b result in 0" )

           else :

               print (c)

         

      AbyB( 2. 0 , 3.0 )

      AbyB( 3.0 , 3.0 )

      Выход:

       -5.0
      a/b приводит к 0 

       

      Наконец ключевое слово в Python

      Python предоставляет ключевое слово finally, которое всегда выполняется после блоков try и exclude. Последний блок всегда выполняется после нормального завершения блока попытки или после завершения блока попытки из-за некоторых исключений.
       

      Синтаксис:

       попытка:
          # Некоторый код
      кроме:
          # Выполняется, если ошибка в
          # пробный блок
      еще:
          # выполнить, если нет исключения
      в конце концов:
          # Какой-то код.....(всегда выполняется) 

      Код:

      Python3

           

      try :

           k = 5 / / 0

           print (k)

      За исключением ZerodivisionError:

      Печать ( "Не удается разделить на ноль" )0100

               

      finally :

          

          

           print ( 'This is always executed'

      Output :

       Нельзя делить на ноль
      Выполняется всегда 

      Статьи по теме:  
       

      • Вопросы вывода
      • Обработка исключений в Python
      • Пользовательские исключения

      Эта статья предоставлена ​​ Mohit Gupta_OMG 😀 . Если вам нравится GeeksforGeeks и вы хотите внести свой вклад, вы также можете написать статью с помощью write.geeksforgeeks.org или отправить ее по адресу [email protected]. Посмотрите, как ваша статья появится на главной странице GeeksforGeeks, и помогите другим гикам.
      Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по теме, обсуждаемой выше.
       


      Более изящная обработка исключений

      Резюме : в этом руководстве вы узнаете, как использовать оператор Python try...except для корректной обработки исключений.

      В Python есть два основных типа ошибок: синтаксические ошибки и исключения.

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

      При написании недопустимого кода Python вы получите синтаксическую ошибку. Например:

       

      текущий = 1 если ток < 10 текущий += 1 9SyntaxError: недопустимый синтаксис

      Язык кода: сеанс оболочки (оболочка)

      В этом примере интерпретатор Python обнаружил ошибку в операторе if , поскольку после него отсутствует двоеточие ( : ).

      Интерпретатор Python показывает имя файла и номер строки, где произошла ошибка, чтобы вы могли ее исправить.

      Исключения

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

      В Python ошибки, возникающие во время выполнения, называются исключениями . Причины исключений в основном связаны со средой, в которой выполняется код. Например:

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

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

      Например, следующая программа рассчитывает рост продаж:

       

      # получить ввод чистых продаж print('Введите чистый объем продаж для') предыдущий = float(input('- Предыдущий период:')) current = float(input('- Текущий период:')) # рассчитать изменение в процентах изменить = (текущий - предыдущий) * 100 / предыдущий # показать результат если изменить > 0: результат = f'Увеличение продаж {абс(изменение)}%' еще: результат = f'Снижение продаж {abs(change)}%' print(result)

      Язык кода: Python (python)

      Как это работает.

      • Сначала предложите пользователям ввести два числа: чистые продажи за предыдущий и текущий периоды.
      • Затем рассчитайте рост продаж в процентах и ​​покажите результат.

      Когда вы запустите программу и введете 120' в качестве чистых продаж за текущий период, интерпретатор Python выдаст следующий вывод:

       

      Введите чистые продажи за текущий период. - Предыдущий период: 100 - Текущий период: 120' Traceback (последний последний вызов): Файл "d:/python/try-except.py", строка 5, в current = float(input('- Текущий период:')) ValueError: не удалось преобразовать строку в число с плавающей запятой: "120"

      Язык кода: сеанс оболочки (оболочка)

      Интерпретатор Python показал трассировку, которая включает подробную информацию об исключении:

      • Путь к файлу исходного кода ( d:/python/try-except. py ), что вызвало исключение.
      • Точная строка кода, вызвавшая исключение ( строка 5 )
      • Оператор, вызвавший исключение current = float(input('- Текущий период:'))
      • Тип исключения ValueError
      • Сообщение об ошибке: ValueError: не удалось преобразовать строку в число с плавающей запятой: "120"

      Поскольку float() не удалось преобразовать строку 120' в число, интерпретатор Python выдал исключение ValueError .

      В Python исключения имеют разные типы, такие как TypeError , NameError и т. д.

      Обработка исключений

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

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

      Для этого вы можете использовать инструкцию Python try...except :

       

      try: # код, который может вызвать ошибку кроме: # обработка ошибок

      Язык кода: Python (python)

      Оператор try...except работает следующим образом:

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

      Следующая блок-схема иллюстрирует оператор try. ..except :

      Таким образом, для обработки исключений с помощью оператора try...except , код, который может вызвать исключение, помещается в предложение try , а код, обрабатывающий исключения, — в предложение , кроме .

      Вот как вы можете переписать программу и использовать оператор try...except для обработки исключения:

       

      try: # получить входные данные о чистых продажах print('Введите чистый объем продаж для') предыдущий = float(input('- Предыдущий период:')) current = float(input('- Текущий период:')) # рассчитать изменение в процентах изменить = (текущий - предыдущий) * 100 / предыдущий # показать результат если изменить > 0: результат = f'Увеличение продаж {абс(изменение)}%' еще: результат = f'Снижение продаж {abs(change)}%' печать (результат) кроме: print('Ошибка! Введите число чистых продаж. ')

      Кодовый язык: Python (python)

      Если вы снова запустите программу и введете чистый объем продаж, который не является числом, программа вместо этого выдаст сообщение, которое вы указали в блоке , кроме :

       

      Введите чистый объем продаж для - Предыдущий период: 100 - Текущий период: 120' Ошибка! Введите число чистых продаж.

      Кодовый язык: Сеанс оболочки (оболочка)

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

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

       

      Введите чистый объем продаж для - Предыдущий период:0 - Текущий период:100 Ошибка! Введите число чистых продаж.

      Язык кода: Shell Session (оболочка)

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

      Оператор try...except позволяет обрабатывать конкретное исключение. Чтобы поймать выбранное исключение, вы помещаете тип исключения после кроме ключевого слова :

       

      попытка: # код, который может вызвать исключение кроме ValueError как ошибки: # код для обработки исключения

      Язык кода: Python (python)

      Например:

       

      try: # получить входные данные о чистых продажах print('Введите чистый объем продаж для') предыдущий = float(input('- Предыдущий период:')) current = float(input('- Текущий период:')) # рассчитать изменение в процентах изменить = (текущий - предыдущий) * 100 / предыдущий # показать результат если изменить > 0: результат = f'Увеличение продаж {абс(изменение)}%' еще: результат = f'Снижение продаж {abs(change)}%' печать (результат) кроме ValueError: print('Ошибка! Введите число чистых продаж. ')

      Язык кода: Python (python)

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

      Однако, если вы введете ноль для чистых продаж за предыдущий период:

       

      Введите чистые продажи для - Предыдущий период:0 - Текущий период: 100

      Кодовый язык: Shell Session (оболочка)

      … вы получите следующее сообщение об ошибке:

       

      Трассировка (последний последний вызов): Файл "d:/python/try-except.py", строка 9, в <модуль> изменить = (текущий - предыдущий) * 100 / предыдущий ZeroDivisionError: деление с плавающей запятой на ноль

      Язык кода: сеанс оболочки (оболочка)

      На этот раз вы получили исключение ZeroDivisionError . Это исключение деления на ноль вызвано следующим утверждением:

       

      изменение = (текущее - предыдущее) * 100 / предыдущее

      Язык кода: Python (python)

      И причина в том, что значение предыдущий равен нулю.

      Обработка нескольких исключений

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

       

      try: # код, который может вызвать исключение кроме Exception1 как e1: # обработка исключения кроме Exception2 как e2: # обработка исключения кроме Exception3 как e3: # обработка исключения

      Язык кода: Python (python)

      Это позволяет по-разному реагировать на каждый тип исключения.

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

       

      попробуйте: # код, который может вызвать исключение кроме (Исключение1, Исключение2): # обработка исключения

      Язык кода: Python (python)

      В следующем примере показано, как использовать try...except для обработки исключений ValueError и ZeroDivisionError :

       

      попробуйте: # получить входные данные о чистых продажах print('Введите чистый объем продаж для') предыдущий = float(input('- Предыдущий период:')) current = float(input('- Текущий период:')) # рассчитать изменение в процентах изменить = (текущий - предыдущий) * 100 / предыдущий # показать результат если изменить > 0: результат = f'Увеличение продаж {абс(изменение)}%' еще: результат = f'Снижение продаж {abs(change)}%' печать (результат) кроме ValueError: print('Ошибка! Введите число чистых продаж. ') кроме ZeroDivisionError: print('Ошибка! Предыдущие чистые продажи не могут быть равны нулю.')

      Кодовый язык: Python (python)

      При вводе нуля для чистых продаж за предыдущий период:

       

      Введите чистые продажи для - Предыдущий период:0 - Текущий период: 120

      Язык кода: Shell Session (оболочка)

      … вы получите следующую ошибку:

       

      Ошибка! Предыдущие чистые продажи не могут быть равны нулю.

      Язык кода: сеанс оболочки (оболочка)

      Рекомендуется выявлять другие общие ошибки, помещая catch Блок исключения в конце списка:

       

      попытка: # получить входные данные о чистых продажах print('Введите чистый объем продаж для') предыдущий = float(input('- Предыдущий период:')) current = float(input('- Текущий период:')) # рассчитать изменение в процентах изменить = (текущий - предыдущий) * 100 / предыдущий # показать результат если изменить > 0: результат = f'Увеличение продаж {абс(изменение)}%' еще: результат = f'Снижение продаж {abs(change)}%' печать (результат) кроме ValueError: print('Ошибка! Введите число чистых продаж. ') кроме ZeroDivisionError: print('Ошибка! Предыдущие чистые продажи не могут быть равны нулю.') кроме исключения как ошибки: печать (ошибка)

      Язык кода: Python (python)

      Сводка

      • Используйте оператор Python try...except для корректной обработки исключений.
      • Максимально используйте определенные исключения в блоке , кроме .
      • Используйте оператор , кроме Exception , чтобы перехватывать другие исключения.

      Считаете ли вы это руководство полезным?

      Учебное пособие по исключениям с примерами кода • Учебное пособие по Python Land

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

      Содержание

      Что такое исключение?

      Исключение — это условие, возникающее во время выполнения программы. Это сигнал о том, что произошло что-то неожиданное. Python представляет исключения объектом определенного типа.

      В Python все встроенные исключения, не связанные с выходом из системы, являются производными от Исключение класса . Исключения имеют свои собственные описательные имена. Например, если вы попытаетесь разделить число на ноль, вы получите исключение ZeroDivisionError , которое также является подклассом класса Exception .

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

       BaseException
       +-- Выход из системы
       +-- Прерывание Клавиатуры
       +-- Исключение
            +-- Ошибка арифметики
            | +-- Ошибка с плавающей запятой
            | +-- Ошибка переполнения
            | +-- ZeroDivisionError
            . .... 

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

      Попробуйте Python, кроме

      Когда происходит что-то неожиданное, мы можем вызвать исключение в момент возникновения ошибки. Когда возникает исключение, Python останавливает текущий поток выполнения и начинает искать обработчик исключений, который может его обработать. Так что же такое обработчик исключений? Здесь в игру вступают операторы try и exclude.

      попробовать.. кроме.. иначе.. наконец

      Как показано на рисунке, мы можем создать блок кода, начав с оператора try. В основном это означает: попробуйте запустить этот код, но может возникнуть исключение .

      После нашего блока try должны следовать один или несколько блоков exclude. Вот где происходит волшебство. Эти блоки исключений могут перехватывать исключение, как мы обычно это называем. На самом деле, многие другие языки программирования используют выражение catch вместо 9. 0099 кроме . Каждый блок , кроме , может обрабатывать определенный тип исключения.

      Помните: классы иерархичны. По этой причине исключения тоже. Следовательно, блоки exclude должны переходить от наиболее конкретных, таких как ZeroDivisionError , к менее конкретным, таким как ArithmeticError .

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

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

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

      Стек вызовов

      Стек вызовов — это упорядоченный список функций, которые выполняются в данный момент. Например, вы можете вызвать функцию A, которая вызывает функцию B, которая вызывает функцию C. Теперь у нас есть стек вызовов, состоящий из A, B и C. Когда C вызывает исключение, Python будет искать обработчик исключения в этом вызове. стек, идущий в обратном направлении от конца к началу. Она может быть в функции C (ближайшей к исключению), в функции B (чуть дальше), в функции A или даже на верхнем уровне программы, где мы вызывали функцию A.

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

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

      Перехват исключений с помощью try, кроме

      Давайте, наконец, напишем реальный код! Чтобы обработать исключение, нам нужно его поймать. Как мы только что узнали, мы можем поймать исключение, используя ключевые слова try и , кроме . Когда возникает исключение, когда мы находимся внутри блока try , выполняется код из блока , кроме .

      Простой пример

      Сначала рассмотрим простой пример. Как вы, надеюсь, знаете, мы не можем делить на ноль. Если мы все равно это сделаем, Python выдаст исключение с именем 9.0099 ZeroDivisionError , который является подклассом ArithmeticError :

       попробуйте:
          печать(2/0)
      кроме ZeroDivisionError:
          print("Нельзя делить на ноль!") 

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

      Также обратите внимание, что Python выводит сообщение об ошибке в stderr, если вы не обрабатываете исключение самостоятельно. В крошке выше это видно, потому что вывод отображается на вкладке «Ошибка», а не на вкладке «Вывод».

      Перехват IOError

      Давайте попробуем другой, более распространенный пример. Ведь кто делит число на ноль, верно?

      Возможны исключения при взаимодействии с внешним миром, т.е. при работе с файлами или сетями. Например, если вы попытаетесь открыть файл с помощью Python, но этот файл не существует, вы получите Исключение IOError . Если у вас нет доступа к файлу из-за разрешений, вы снова получите исключение IOError . Давайте посмотрим, как обрабатывать эти исключения.

      Назначение

      Пожалуйста, сделайте следующее:

      1. Запустите приведенный ниже код, обратите внимание на имя файла (его не существует). Посмотрите, что происходит.
      2. Измените имя файла на файл myfile.txt и снова запустите код. Что происходит?
      Назначение try-except Python

      В качестве альтернативы, вот код для копирования/вставки:

       попробуйте:
          # Открыть файл в режиме только для чтения
          с open("not_here.txt", 'r') как f:
              f.write("Привет, мир!")
      кроме IOError как e:
          print("Произошла ошибка:", e) 

      Ответы

      В первом случае файл не найден. Вы должны получить этот вывод:

       Произошла ошибка: [Errno 2] Нет такого файла или каталога: 'not_here.txt' 

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

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

      Хотя это и ошибка, она не записывается в вывод stderr операционной системы. Это потому, что мы сами обработали исключение. Если вы полностью удалили try.., кроме кода, а затем попытались записать в файл в режиме только для чтения, Python поймает ошибку, заставит программу завершиться и покажет это сообщение:

       Traceback (последний вызов последний):
        Файл «tryexcept.py», строка 3, в 
          f.write("Привет, мир!")
      io.UnsupportedOperation: недоступно для записи 

      Блоки finally и else

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

      Блок finally в try-except

      Блок finally выполняется независимо от того, возникает исключение или нет. Наконец, блоки полезны, например, когда вы хотите закрыть файл или сетевое соединение независимо от того, что происходит. В конце концов, вы хотите очистить ресурсы, чтобы предотвратить утечку памяти.

      Вот пример этого на работе, в котором мы открываем файл без использования оператора with , что заставляет нас закрыть его самостоятельно:

       попробуйте:
          # Открыть файл в режиме записи
          f = открыть ("мой файл.txt", 'w')
          f.write("Привет, мир!")
      кроме IOError как e:
          print("Произошла ошибка:", e)
      в конце концов:
          print("Закрытие файла сейчас")
          f.close() 

      Вы также можете попробовать этот интерактивный пример:

      Вы должны увидеть сообщение «Закрытие файла сейчас», напечатанное на вашем экране. Теперь измените режим письма на «r» вместо «w». Вы получите сообщение об ошибке, так как файл не существует. Несмотря на это исключение, мы все равно пытаемся закрыть файл благодаря блоку finally. Это, в свою очередь, тоже идет не так: NameError  вызвано исключение, поскольку файл никогда не открывался и, следовательно, f  не существует. Это можно исправить с помощью вложенной попытки .. кроме NameError . Попробуйте сами.

      Блок else в try-except

      В дополнение к блокам кроме и finally вы можете добавить блок else. Блок else выполняется только тогда, когда не возникает никаких исключений. Таким образом, он отличается от блока finally тем, что finally выполняется, даже если возникает исключение.

      Когда следует использовать блок else ? И почему бы вам просто не добавить дополнительный код в блок try ? Хорошие вопросы!

      В соответствии с руководством по Python лучше использовать предложение else, чем добавлять дополнительный код в предложение try. Но почему? Причина в том, что это позволяет избежать случайного перехвата исключения, которое не было вызвано кодом, защищенным операторами try и exclude. Я признаю, что сам не очень часто использую блоки else. Кроме того, я нахожу их несколько запутанными, особенно для людей, говорящих на других языках.

      Общие исключения Python

      Некоторые исключения настолько распространены, что вы неизбежно столкнетесь с ними. Вот несколько наиболее распространенных из них:

      Имя исключения Когда вы столкнетесь с этим Пример ситуации, которая поднимает исключение
      SYNTAXERRORSERRISER
      7778
      7778
      7778
      7778
      . ошибка в синтаксисе Python. Если это исключение не будет перехвачено, это приведет к выходу из интерпретатора Python. pritn('test')
      KeyError Возникает, когда ключ не найден в словаре. d = { 'a': 1}
      d['b']
      IndexError Возникает, когда индекс выходит за пределы диапазона. lst = [1, 2, 3]
      lst[10]
      KeyboardInterrupt Возникает, когда пользователь нажимает клавишу прерывания (Ctrl+C) Нажатие control+c 98
      Некоторые общие исключения, с которыми вы столкнетесь в какой-то момент своей карьеры

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

      Рекомендации по работе с исключениями

      Теперь, когда мы знаем механизм обработки исключений, я хотел бы поделиться с вами несколькими рекомендациями.

      Не используйте пустые блоки, кроме блоков

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

       попробуйте:
          ...
      кроме:
          print("Произошла ошибка:") 

      Вы можете встретить это в примерах кода в Интернете. Если вы это сделаете, сделайте привычкой улучшать обработку исключений. Зачем вам это нужно и как вы можете улучшить код, подобный приведенному выше примеру?

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

      Так что хотя синтаксис разрешен, я не рекомендую его. Например, вы также поймаете KeyboardInterrupt 9.0100 и Исключения SystemExit , препятствующие выходу вашей программы. Вместо этого используйте блок try со списком явных исключений, которые, как вы знаете, вы можете обработать. Или, если очень нужно, ловите базовый класс Exception для обработки почти всех обычных исключений, но не системных.

      Если вы любите приключения, вы можете попробовать перехватить все исключения и посмотреть, что произойдет:

       from time import sleep
      пока верно:
          пытаться:
              print("Попробуй остановить меня")
              спать(1)
          кроме:
              print("Не останавливай меня сейчас, я так хорошо провожу время!") 

      Возможно, вам придется закрыть терминал, чтобы остановить эту программу. Теперь измените блок exclude, чтобы поймать Exception . Вы по-прежнему будете перехватывать почти все исключения, но программа завершится при системных исключениях, таких как KeyboardInterrupt и SystemExit :

       из time import sleep
      пока верно:
          пытаться:
              print("Попробуй остановить меня")
              спать(1)
          кроме исключения:
              print("Что-то пошло не так") 

      Лучше попросить прощения

      В Python часто можно встретить шаблон, в котором люди просто пробуют, работает ли что-то, а если нет, перехватывают исключение. Другими словами, лучше попросить прощения, чем разрешения. Это отличается от других языков, где вы предпочтительно спрашиваете разрешения. Например, в Java исключения могут замедлить вашу программу, и вы «спрашиваете разрешения», выполняя проверки объекта, а не просто пытаясь.

      Чтобы сделать это более конкретным: в Python мы часто просто пытаемся получить доступ к ключу в словаре. Если ключ не существует, мы получим исключение и обработаем его. Предположим, мы только что преобразовали какой-то предоставленный извне JSON в словарь и теперь начинаем его использовать:

       импорт json
      user_json = '{"имя": "Джон", "возраст": 39}'
      пользователь = json.loads(user_json)
      пытаться:
          печать (пользователь ['имя'])
          печать (пользователь ['возраст'])
          печать (пользователь ['адрес'])
          ...
      кроме KeyError как e:
          print("В пользовательском объекте отсутствуют поля: ", e)
          # Правильно обработайте ошибку
          ... 

      Это напечатает ошибку:

       В пользовательском объекте отсутствуют поля: «адрес» 

      Мы могли бы добавить три проверки ( если «имя» в пользователе , если «возраст» в пользователе и т. д.), чтобы убедиться, что все поля есть. Но это не очень хорошая практика. Это потенциально вводит много кода только для проверки существования ключей. Вместо этого мы просим прощения в блоке exclude, который намного чище и читабельнее. И если вы беспокоитесь о производительности: исключения не занимают столько циклов ЦП в Python. Многие сравнения на самом деле медленнее, чем перехват одного исключения (если оно вообще происходит!).

      Создание пользовательских исключений

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

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

       class UserNotFoundError(Exception):
          пройти 

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

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

      Мы будем использовать этот класс в следующем примере.

      Инициирование (или генерация) исключений

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

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

       класс UserNotFoundError (Исключение):
          проходить
      защита fetch_user (user_id):
          # Здесь вы должны получить данные из какой-либо БД, например:
          # пользователь = db.get_user(user_id)
          # Чтобы сделать этот пример работоспособным, установим его в None
          пользователь = нет
          если пользователь == Нет:
              поднять UserNotFoundError (f'Пользователь {user_id} не в базе данных')
          еще:
              вернуть пользователя
      пользователи = [123, 456, 789]
      для user_id в пользователях:
          пытаться:
              fetch_user (user_id)
          кроме UserNotFoundError как e:
              print("Произошла ошибка: ", e) 
      Присвоение

      Вот небольшое задание. Вместо этого мы могли бы использовать обычный объект Exception. Таким образом, нам не нужно определять пользовательский. Почему это плохая идея?

      Ответ

      На самом деле вы можете вызвать обычное исключение, например. с поднять Exception('Пользователь не найден') . Но если вы это сделаете, вам нужно перехватить все исключения типа Exception. А их, как известно, немало. Скорее всего, вы непреднамеренно поймаете какое-то другое исключение, которое не сможете обработать. Например, клиент базы данных может выдать DatabaseAuthenticationError , который также является подклассом Exception .

      Как распечатать исключение Python

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

       try:
          . ..
      кроме Исключения как e:
          print("Произошла ошибка: ", e) 

      Если вы хотите распечатать стек вызовов, как это делает Python, когда вы не перехватываете исключение самостоятельно, вы можете импортировать модуль трассировки:

       трассировка импорта
      пытаться:
          ...
      кроме исключения:
          traceback.print_exc()
       

      Продолжайте учиться

      Вот еще несколько ресурсов для углубления ваших знаний:

      • Статья в моем блоге «Как не обрабатывать исключения в Python»
      • Введение в функции Python
      • Объекты и классы и наследование Python
      • документация по исключениям.
      • Официальная документация по ошибкам.

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