【问题标题】:How to suppress displaying the parent exception (the cause) for subsequent exceptions如何禁止显示后续异常的父异常(原因)
【发布时间】:2015-07-25 23:05:39
【问题描述】:

我知道raise ... from None 并已阅读How can I more easily suppress previous exceptions when I raise my own exception in response?

但是,如何在不控制从 except 子句执行的代码的情况下实现相同的效果(抑制“在处理上述异常期间,发生另一个异常”消息)?我认为 sys.exc_clear() 可以用于此,但 Python 3 中不存在该函数。

我为什么要问这个?我有一些简单的缓存代码,看起来像(简化):

try:
    value = cache_dict[key]
except KeyError:
    value = some_api.get_the_value_via_web_service_call(key)
    cache_dict[key] = value

当API调用出现异常时,输出如下:

Traceback (most recent call last):
  File ..., line ..., in ...
KeyError: '...'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ..., line ..., in ...
some_api.TheInterestingException: ...

这是一种误导,因为原来的 KeyError 根本不是一个错误。我当然可以通过将 try/except (EAFP) 更改为对密钥是否存在的测试 (LBYL) 来避免这种情况,但这不是非常 Pythonic 并且对线程不太友好(并不是说上面是线程安全的,但那是题外话)。

期望 some_api 中的所有代码都将它们的 raise X 更改为 raise X from None 是不合理的(甚至在所有情况下都没有意义)。是否有一个干净的解决方案来避免错误消息中出现不需要的异常链?

(顺便问一下,额外的问题:我在示例中使用的缓存基本上相当于cache_dict.setdefault(key, some_api.get_the_value_via_web_service_call(key)),如果只有 setdefault 的第二个参数可以是仅在需要设置值时才调用的可调用对象. 没有更好/规范的方法吗?)

【问题讨论】:

标签: python exception python-3.x exception-handling


【解决方案1】:

您可以尝试自己隐藏上下文:

try:
    value = cache_dict[key]
except KeyError:
    try:
        value = some_api.get_the_value_via_web_service_call(key)
    except Exception as e:
        e.__context__ = None
        raise

    cache_dict[key] = value

【讨论】:

    【解决方案2】:

    这里有几个选项。

    首先,orlp 建议的更简洁的版本:

    try:
        value = cache_dict[key]
    except KeyError:
        try:
            value = some_api.get_the_value(key)
        except Exception as e:
            raise e from None
        cache_dict[key] = value
    

    对于第二个选项,我假设有一个 return value 隐藏在你没有显示的某个地方:

    try:
        return cache_dict[key]
    except KeyError:
        pass
    value = cache_dict[key] = some_api.get_the_value(key)
    return value
    

    第三个选项,LBYL:

    if key not in cache_dict:
        cache_dict[key] = some_api.get_the_value(key)
    return cache_dict[key]
    

    对于附加问题,定义你自己的 dict 子类来定义__missing__

    class MyCacheDict(dict):
    
        def __missing__(self, key):
            value = self[key] = some_api.get_the_value(key)
            return value
    

    希望这会有所帮助!

    【讨论】:

    • 谢谢。您的第一个建议的问题是 1)它可能抑制的内容超出了应有的范围(也许 API 的代码中有一个有用的上下文应该列在 tracebank 中),以及 2)它掩盖了异常的来源(而不是将 API 中的模块视为回溯中的最后一行,用户现在将看到我的代码,而 API 将是最后一行)。尽管如此,在没有 sys.exc_clear() 的情况下,它是我迄今为止看到的所有解决方案中的首选解决方案。
    • 您关于抑制可能有用的上下文的观点绝对是正确的,因此可能不应该使用该建议。但是,您关于模糊来源的观点是无效的:完全相同的异常,回溯和所有,都被重新提出,只是没有它的上下文。我个人更愿意使用带有__missing__ 或LBYL 选项的dict 子类。
    【解决方案3】:

    这是@Zachary 的第二个选项的一个版本,它的使用更简单一些。首先,dict 的辅助子类,它在“未命中”时返回一个标记值,而不是抛出异常:

    class DictCache(dict):
        def __missing__(self, key):
            return self
    

    然后在使用中:

    cache = DictCache()
    ...
    value = cache[K]
    if value is cache:
        value = cache[K] = some_expensive_call(K)
    

    注意使用“is”而不是“==”以确保不会与有效条目发生冲突。

    如果被分配的东西是一个简单的变量(即“value”而不是另一个变量“x.value”的属性),你甚至可以只写两行:

    if (value := cache[K]) is cache:
        value = cache[K] = some_expensive_call(K)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-10
      相关资源
      最近更新 更多