通常,如果您尝试为同一关键字参数传递多个值,您会收到 TypeError:
In [1]: dict(id=1, **{'id': 2})
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [1], in <cell line: 1>()
----> 1 dict(id=1, **{'id': 2})
TypeError: dict() got multiple values for keyword argument 'id'
但是,如果您在处理另一个异常时执行此操作,则会收到 KeyError:
In [2]: try:
...: raise ValueError('foo') # no matter what kind of exception
...: except:
...: dict(id=1, **{'id': 2}) # raises: KeyError: 'id'
...:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [2], in <cell line: 1>()
1 try:
----> 2 raise ValueError('foo') # no matter what kind of exception
3 except:
ValueError: foo
During handling of the above exception, another exception occurred:
KeyError Traceback (most recent call last)
Input In [2], in <cell line: 1>()
2 raise ValueError('foo') # no matter what kind of exception
3 except:
----> 4 dict(id=1, **{'id': 2})
KeyError: 'id'
这里发生了什么?一个完全不相关的异常如何影响dict(id=1, **{'id': 2})
抛出什么样的异常?
对于上下文,我在调查以下错误报告时发现了此行为:https://github.com/tortoise/tortoise-orm/issues/1583
这已在 CPython 3.11.8、3.10.5 和 3.9.5 上重现。
这看起来像是一个 Python 错误。
TypeError
应该通过检测和替换初始值来提高工作效率的代码KeyError
,但该代码无法正常工作。当异常发生在另一个异常处理程序中间时,应该引发 的代码TypeError
无法识别KeyError
. 它最终让该KeyError
通过,而不是用TypeError
.由于异常实现的变化,该错误似乎在 3.12 中消失了。
以下是 CPython 3.11.8 源代码的深入研究。3.10 和 3.9 上也有类似的代码。
dis
正如我们通过使用模块检查字节码所看到的dict(id=1, **{'id': 2})
:Python 使用
DICT_MERGE
操作码来合并两个字典,以构建最终的关键字参数字典。相关部分代码
DICT_MERGE
如下:它用于
_PyDict_MergeEx
尝试合并两个字典,如果失败(并引发异常),它用于format_kwargs_error
尝试引发不同的异常。当第三个参数为 时
_PyDict_MergeEx
,2
该函数将在辅助函数KeyError
内引发重复键dict_merge
。这就是KeyError
来自的地方。一旦
KeyError
被提出,format_kwargs_error
就有将其替换为 的工作TypeError
。它尝试使用以下代码来做到这一点:但此代码正在寻找非规范化异常,这是一种表示未暴露给 Python 级代码的异常的内部方式。它期望异常值是一个 1 元素元组,其中包含引发 KeyError 的键,而不是实际的异常对象。
C 代码内部引发的异常通常是非规范化的,但如果它们在 Python 处理另一个异常时发生则不是。非规范化异常无法处理异常链,异常链在异常处理程序内引发的异常会自动发生。在这种情况下,内部
_PyErr_SetObject
例程将自动规范化异常:由于
KeyError
已经标准化,format_kwargs_error
不明白它在看什么。它让KeyError
通过,而不是提高它TypeError
应该的。在 Python 3.12 上,情况有所不同。内部异常表示已更改,因此任何引发的异常始终都会标准化。因此,Python 3.12 版本
format_kwargs_error
会查找规范化异常而不是非规范化异常,如果_PyDict_MergeEx
引发了 aKeyError
,代码将识别它: