当前位置: 代码迷 >> python >> 为什么python自省不知道函数中的异常?
  详细解决方案

为什么python自省不知道函数中的异常?

热度:50   发布时间:2023-06-27 21:32:12.0

当我处理一些代码异常代码时,我想知道为什么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的设计?

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 在有意义的地方使用标准异常之一( ValueErrorTypeErrorIndexErrorKeyError等),或者通过从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 Nameraise 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]