这看起来像一个Python错误。
应该引发的代码
TypeError
通过检测和替换初始值来工作
KeyError
,但此代码无法正常工作。当异常发生在另一个异常处理程序的中间时,应该引发
类型错误
无法识别
KeyError
。它最终让
KeyError
通过,而不是将其替换为
类型错误
.
由于异常实现中的更改,错误似乎在3.12上消失了。
下面是对CPython 3.11.8源代码的深入了解。3.10和3.9中也存在类似的代码。
正如我们可以通过使用
dis
用于检查字节码的模块
dict(id=1, **{'id': 2})
:
In [1]: import dis
In [2]: dis.dis("dict(id=1, **{'id': 2})")
1 0 LOAD_NAME 0 (dict)
2 LOAD_CONST 3 (())
4 LOAD_CONST 0 ('id')
6 LOAD_CONST 1 (1)
8 BUILD_MAP 1
10 LOAD_CONST 0 ('id')
12 LOAD_CONST 2 (2)
14 BUILD_MAP 1
16 DICT_MERGE 1
18 CALL_FUNCTION_EX 1
20 RETURN_VALUE
Python使用
DICT_MERGE
合并两个dict的操作码,以构建最终的关键字参数dict。
的相关部分
DICT_MERGE
code
如下所示:
if (_PyDict_MergeEx(dict, update, 2) < 0) {
format_kwargs_error(tstate, PEEK(2 + oparg), update);
Py_DECREF(update);
goto error;
}
它使用
_PyDict_MergeEx
尝试合并两个dict,如果失败(并引发异常),则使用
format_kwargs_error
试图筹集
不同的
例外
当第三个参数
_PyDict_MergeEx
是
2
,该函数将引发
KeyError
对于重复的密钥,在
dict_merge
helper函数。这就是
KeyError
来自。
一旦
KeyError
被升高,
format_kwargs_error
的工作是将其替换为
类型错误
。它试图用
the following code
:
else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
PyObject *exc, *val, *tb;
_PyErr_Fetch(tstate, &exc, &val, &tb);
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
但此代码正在查找
未规范化
exception,一种表示未暴露于Python级别代码的异常的内部方式。它希望异常值是一个包含引发KeyError的键的1元素元组,而不是实际的异常对象。
C代码内部引发的异常通常是未规范化的,但如果它们发生在Python处理另一个异常时,则不会进行规范化。无法处理未规范化的异常
exception chaining
,这对于异常处理程序内部引发的异常会自动发生。在这种情况下,内部
_PyErr_SetObject
例行遗嘱
automatically normalize
例外情况:
exc_value = _PyErr_GetTopmostException(tstate)->exc_value;
if (exc_value != NULL && exc_value != Py_None) {
/* Implicit exception chaining */
Py_INCREF(exc_value);
if (value == NULL || !PyExceptionInstance_Check(value)) {
/* We must normalize the value right now */
自
KeyError
已经被归一化,
format_kwargs_error
不明白它在看什么。它让
KeyError
通过,而不是提高
类型错误
应该是的。
在Python 3.12上,情况有所不同。内部异常表示形式已更改,因此任何引发的异常都是
总是
归一化。因此,Python 3.12版本的
format_kwargs_error
查找规范化异常而不是未规范化异常,如果
_PyDict_MergeEx
已引发
KeyError
,代码将识别它:
else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
PyObject *exc = _PyErr_GetRaisedException(tstate);
PyObject *args = ((PyBaseExceptionObject *)exc)->args;
if (exc && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1) {