При использовании оператора try/except
без звездочки, за раз можно отловить только одну ошибку/исключение. Возможно, произошла бы другая ошибка, если бы код продолжал работать. Но Python обычно сообщает только о первой обнаруженной ошибке. Бывают ситуации, когда имеет смысл сообщать сразу о нескольких ошибках:
try/except*
со звездочкой.Новое в Python 3.11
Изменено в Python 3.12: Когда конструкция
try-except*
обрабатывает всю группуExceptionGroup
и вызывает еще одно исключение, это исключение больше не помещается вExceptionGroup
. Это поведение было изменено в версии 3.11.4. но не задокументировано (Предоставлено Ирит Катриэль.)
Специальный оператор try/except*
со звездочкой (новое в Python 3.11) используются для обработки классов групп исключений ExceptionGroup
и BaseExceptionGroup
(новое в Python 3.11), которые соответствуют их подгруппам на основе типов содержащихся исключений.
Классы групп исключений используются, когда необходимо вызвать несколько несвязанных исключений. Они являются частью иерархии исключений, поэтому их также можно обрабатывать обычными операторами try/except
, как и все другие исключения.
Тип исключения для сопоставления интерпретируется так же, как и в случае с try/except
, но в случае групп исключений можно иметь частичное совпадение, когда тип соответствует некоторым исключениям в группе. Это означает, что можно использовать несколько специальных операторов except*
, каждое из которых будет обрабатывать часть группы исключений. Каждое предложение/оператор except*
со звёздочкой выполняется один раз и обрабатывает группу исключений из всех соответствующих исключений. Каждое исключение в группе обрабатывается не более чем одним предложением except*
, первым, которое ему соответствует.
Если ничего не понятно, то читайте ниже раздел "Понимание того, как работает
except*
со звездочкой".
try: raise ExceptionGroup("eg", [ValueError(1), TypeError(2), OSError(3), OSError(4)]) except* TypeError as e: print(f'caught {type(e)} with nested {e.exceptions}') except* OSError as e: print(f'caught {type(e)} with nested {e.exceptions}') # caught <class 'ExceptionGroup'> with nested (TypeError(2),) # caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4)) # + Exception Group Traceback (most recent call last): # | File "<stdin>", line 2, in <module> # | ExceptionGroup: eg # +-+---------------- 1 ---------------- # | ValueError: 1 # +------------------------------------
ВНИМАНИЕ! Нельзя смешивать операторы
except
(без звездочки) иexcept*
(со звездочкой) в одном и том же блокеtry
. Операторыbreak
,continue
иreturn
не могут использоваться в предложенииexcept*
.
Любые оставшиеся исключения, которые не были обработаны каким-либо except*
(со звездочкой) повторно вызываются в конце и объединяются в группу исключений вместе со всеми исключениями, которые были вызваны в предложениях except*
.
Специальный оператор except*
(со звездочкой) должен иметь соответствующий тип, и этот тип не может быть подклассом BaseExceptionGroup
.
Для обработки нескольких исключений из ExceptionGroup
в одном предложении, также нужно использовать специальный оператор except*
(со звездочкой), как показано ниже:
try: raise ExceptionGroup('Example ExceptionGroup', ( TypeError('Example TypeError'), ValueError('Example ValueError'), KeyError('Example KeyError'), AttributeError('Example AttributeError') )) except* TypeError: ... except* ValueError as e: ... # нескольких исключений в одном предложении except* (KeyError, AttributeError) as e: ...
С этой новой функциональностью, возможности обработки исключений - безграничны.
except*
со звездочкой.Исключения определяются в иерархии. Например, ModuleNotFoundError
- наследуется от ImportError
, который в свою очередь наследуется от Exception
.
Примечание. Так как большинство исключений наследуются от
Exception
, можно конечно попытаться упростить обработку ошибок, используя только блоки сException
. Это плохая идея. Необходимо чтобы блоки исключений были как можно более конкретными, во избежании возникновения непредвиденных ошибок.
При использовании обычного except
без звёздочки, первый соответствующий ошибке except
вызовет обработку исключения:
>>> try: ... import no_such_module ... except ImportError as err: ... print(f"ImportError: {err.__class__}") ... except ModuleNotFoundError as err: ... print(f"ModuleNotFoundError: {err.__class__}") # ImportError: <class 'ModuleNotFoundError'>
Когда импортируется несуществующий модуль, то Python3 вызовет исключение ModuleNotFoundError
. Но так как класс исключения ModuleNotFoundError
наследуется от ImportError
, то обработчик ошибок инициирует предложение except ImportError as err:
, которое расположено первым.
Обратите внимание, что:
except
,except
.Если кто-то раньше имел дело с исключениями, то такое поведение кажется интуитивно понятным. Однако группы исключений ведут себя по-другому.
Хотя при использовании обычного except
одновременно активно не более одного исключения, можно связать связанные исключения в цепочку. Эта цепочка была введена для Python 3.0. В качестве примера смотрим, что произойдет, если вызвать новое исключение при обработке ошибки:
>>> try: ... "3" + 11 ... except TypeError: ... raise ValueError(654) # вызов нового исключения # Traceback (most recent call last): # ... # TypeError: can only concatenate str (not "int") to str # # During handling of the above exception, another exception occurred: # # Traceback (most recent call last): # ... # ValueError: 654
Обратите внимание, что во время обработки исключения TypeError
произошло другое исключение. Предложение except TypeError:
вызвало трассировку, представляющую исходную ошибку, вызванную кодом. Затем есть еще одна трассировка, представляющая новую ошибку ValueError
, которая вызывается принудительно при помощи raise
при обработке ошибки TypeError
.
Можно использовать цепочку исключений для создания нескольких исключений одновременно. Обратите внимание, что этот механизм предназначен для связанных исключений, особенно когда одно исключение возникает во время обработки другого. Такое поведение отличается от варианта использования группы исключений. Группы исключений объединяют несвязанные исключения в том смысле, что они возникают независимо друг от друга. При обработке связанных исключений можно поймать и обработать только последнюю ошибку в цепочке.
В отличие от большинства других исключений, группы исключений при инициализации принимают два аргумента:
Последовательность подисключений может включать другие группы исключений, но не может быть пустой:
>>> ExceptionGroup("one error", [ValueError(654)]) # ExceptionGroup('one error', [ValueError(654)]) >>> ExceptionGroup("two errors", [ValueError(654), TypeError("int")]) # ExceptionGroup('two errors', [ValueError(654), TypeError('int')]) >>> ExceptionGroup("nested", ... [ ... ValueError(654), ... ExceptionGroup("imports", ... [ ... ImportError("no_such_module"), ... ModuleNotFoundError("another_module"), ... ] ... ), ... ] ... ) >>> ExceptionGroup('nested', [ValueError(654), ExceptionGroup('imports', # [ImportError('no_such_module'), ModuleNotFoundError('another_module')])]) # Последовательность подисключений # не может быть пустой >>> ExceptionGroup("no errors", []) # Traceback (most recent call last): # ... # ValueError: second argument (exceptions) must be a non-empty sequence
В этом примере создается несколько разных групп исключений, которые показывают, что группы исключений могут содержать одно исключение, несколько исключений и даже другие группы исключений.
Трассировка группы исключений отформатирована так, чтобы четко показать структуру внутри группы. Увидеть трассировку можно, когда происходит вызов группы исключений:
>>> raise ExceptionGroup("nested", ... [ ... ValueError(654), ... ExceptionGroup("imports", ... [ ... ImportError("no_such_module"), ... ModuleNotFoundError("another_module"), ... ] ... ), ... TypeError("int"), ... ] ... ) # + Exception Group Traceback (most recent call last): # | ... # | ExceptionGroup: nested (3 sub-exceptions) # +-+---------------- 1 ---------------- # | ValueError: 654 # +---------------- 2 ---------------- # | ExceptionGroup: imports (2 sub-exceptions) # +-+---------------- 1 ---------------- # | ImportError: no_such_module # +---------------- 2 ---------------- # | ModuleNotFoundError: another_module # +------------------------------------ # +---------------- 3 ---------------- # | TypeError: int # +------------------------------------
В трассировке перечислены все исключения, входящие в группу исключений. Кроме того, вложенная древовидная структура исключений внутри группы указывается как графически, так и путем перечисления количества подисключений в каждой группе.
Класс ExceptionGroup
также может вызываться как обычное исключение Python3, т.е. оператором except
без звездочки.
>>> try: ... raise ExceptionGroup("group", [ValueError(654)]) ... except ValueError: ... print("Handling ValueError") # + Exception Group Traceback (most recent call last): # | ... # | ExceptionGroup: group (1 sub-exception) # +-+---------------- 1 ---------------- # | ValueError: 654 # +------------------------------------
Даже если группа исключений содержит ValueError
, нельзя обработать ее с помощью except ValueError
. Для этого необходимо использовать синтаксис exclude*
(со звездочкой). У групп исключений есть несколько атрибутов и методов, которых нет у обычных исключений. В частности, можно получить доступ к .exceptions
, чтобы получить кортеж всех подисключений в группе. Перепишем последний пример следующим образом:
>>> try: ... raise ExceptionGroup("group", [ValueError(654)]) ... except* ValueError: ... print("Handling ValueError") ... except* TypeError: ... print("Handling TypeError") # Handling ValueError
Каждое предложение except*
обрабатывает группу исключений, которая является подгруппой исходной группы исключений и содержит все исключения, соответствующие данному типу ошибки. Рассмотрим немного более сложный пример:
>>> try: ... raise ExceptionGroup( ... "group", [TypeError("str"), ValueError(654), TypeError("int")] ... ) ... except* ValueError as eg: ... print(f"Handling ValueError: {eg.exceptions}") ... except* TypeError as eg: ... print(f"Handling TypeError: {eg.exceptions}") # Handling ValueError: (ValueError(654),) # Handling TypeError: (TypeError('str'), TypeError('int'))
Обратите внимание, что в этом примере срабатывают оба предложения
except*
. Это отличается от обычных исключений, где одновременно срабатывает не более одного предложения!
В конечном итоге можно лишь частично обработать группу исключений. Например, обработать только ValueError
из предыдущего примера:
>>> try: ... raise ExceptionGroup( ... "group", [TypeError("str"), ValueError(654), TypeError("int")] ... ) ... except* ValueError as eg: ... print(f"Handling ValueError: {eg.exceptions}") # Handling ValueError: (ValueError(654),) # + Exception Group Traceback (most recent call last): # | ... # | ExceptionGroup: group (2 sub-exceptions) # +-+---------------- 1 ---------------- # | TypeError: str # +---------------- 2 ---------------- # | TypeError: int # +------------------------------------
В этом случае обрабатывается ValueError
. Но это оставляет две необработанные ошибки в группе исключений. Затем эти ошибки всплывают и создают трассировку. Обратите внимание, что ValueError
не является частью трассировки, т.к. она уже обработана.
Можно видеть, что оператор except*
со звездочкой ведет себя иначе, чем except
без звездочки:
except*
, except*
, соответствующий исключению, удаляют эту ошибку из группы исключений.Эти изменения делают работу с несколькими одновременными ошибками более удобной.