Python - это динамический язык, и вы не можете заранее знать, какие исключения может генерировать функция.
Возьмите этот пример:
def throw(exception):
raise exception
Какое исключение будет выполнять эта функцияподнять?Я могу использовать throw(ValueError)
или throw(TypeError('foobar'))
, и оба будут работать и являются действительными Python:
>>> throw(ValueError)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in throw
ValueError
>>> throw(TypeError('foobar'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in throw
TypeError: foobar
Исключениями являются только классы и экземпляры .Текущие версии Python требуют, чтобы класс исключений был производным от BaseException
, но в старых версиях Python вы даже можете использовать строки для исключений (raise "Your mother was a hamster"
).
И поскольку они выглядят как глобальные ине зарезервированные имена, вы можете назначать различные исключения для имен .Следующее также является допустимым синтаксисом Python:
>>> def oops():
... raise ValueError('Oops')
...
>>> ValueError = TypeError
>>> oops()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in oops
TypeError: Oops
Именно поэтому функции Python не могут раскрыть, какие исключения они вызывают.
Обратите внимание, что никогда не бывает веской причины использовать обычный Exception
,Используйте одно из стандартных исключений, если они имеют смысл (ValueError
, TypeError
, IndexError
, KeyError
и т. Д.), Или создайте свои собственные специфичные для API исключения, создав подклассы из Exception
или более конкретный подкласс исключений.
Затем document Ваш API правильно.Укажите, какие исключения следует ожидать разработчику, где это необходимо.Стандартные исключения не должны быть прописаны;вполне очевидно, что функция, которая работает только со строками, выдаст TypeError
, если вместо этого вы передадите файловый объект.
Вы можете использовать иерархию классов исключений в своем бизнес-приложении, если вам нужно перехватить несколько типов:
class BusinessException(Exception):
"""The base exception for all of Business APIs"""
class SpecificBusinessException(BusinessException):
"""Exception that indicates a specific problem occurred"""
class DifferenBusinessException(BusinessException):
"""Exception that indicates a different specific problem occurred"""
затем поднимите подклассовые исключения и поймайте BusinessException
для обработки всех или поймайте только определенные подклассы для настройки обработки.
Если вы должны выяснитькакие коды исключений поднимают и принимают риски, связанные с возможностью изменения имен динамическим языком, тогда вы можете использовать анализ абстрактного синтаксического дерева (AST) , чтобы хотя бы найти некоторую информацию оисключения.Для прямых операторов raise Name
и raise Name(args..)
извлечение этих имен или вызовов путем обхода AST является по меньшей мере относительно простым:
import builtins
import inspect
import ast
class ExceptionExtractor(ast.NodeVisitor):
def __init__(self):
self.exceptions = []
def visit_Raise(self, node):
if node.exc is None:
# plain re-raise
return
exc_name = node.exc
if isinstance(exc_name, ast.Call):
exc_name = exc_name.func
if not (isinstance(exc_name, ast.Name) and
isinstance(exc_name.ctx, ast.Load)):
# not a raise Name or raise Name(...)
return
self.exceptions.append(exc_name.id)
def global_exceptions_raised(func):
"""Extract the expressions used in raise statements
Only supports raise Name and raise Name(...) forms, and
only if the source can be accessed. No checks are made for the
scope of the name.
returns references to those exception names that can be loaded from
the function globals.
"""
source = inspect.getsource(func)
tree = ast.parse(source)
extractor = ExceptionExtractor()
extractor.visit(tree)
fglobals = {**func.__globals__, **vars(builtins)}
return [fglobals[name] for name in extractor.exceptions if name in fglobals]