问题描述
当我处理一些代码异常代码时,我想知道为什么python没有在它的内省系统中包含函数会引发什么异常。例如,当我必须使用一个函数引用许多其他会引发不同异常的函数时,我有考虑在我的业务逻辑中发生的所有事情。 像这样:
def a():
raise Exception('exception1')
def b():
a()
raise Exception('exception2')
def c():
b()
raise Exception('exception3')
def business():
try:
c()
except Exception as e:
pass
我必须继续挖掘它们之间的函数调用,我才能知道这个代码块中可能会引发什么。内省系统没有异常信息。
而且据我所知,Java 会在函数定义中显式标注“Throw”,IDE 和程序员可以很容易地知道我应该处理什么样的异常。
如果我能知道对象本身的所有异常会更好,例如:
all_exception = obj.__exceptions__()
所以,我的问题是,为什么 python 不在函数对象中包含异常内省。
谁能解释一下python的设计?
1楼
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"
BaseException
raise "Your mother was a hamster"
)。
并且因为它们被视为全局变量而不是保留名称,所以您可以为 names 分配不同的例外。 以下也是合法的 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
等),或者通过从Exception
或更具体的异常子类进行子类化来创建您自己的特定于 API 的异常。
然后正确记录您的 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
以处理所有异常,或仅捕获特定子类以自定义处理。
如果您必须弄清楚代码会引发什么异常并接受动态语言能够更改名称所涉及的风险,那么您可以使用来至少找到有关异常的一些信息。
对于直接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]