
CPython 属性访问错误信息增强AttributeError 如何基于 name 与 obj 自动生成默认消息【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython导读CPython 在其异常系统中为AttributeError引入了默认错误消息的智能生成机制当异常实例同时携带name属性名与obj所属对象两个属性且构造时未提供自定义位置参数或仅传入一个与name相同的位置参数时解释器会自动合成形如ClassName object has no attribute attr的完整错误信息而不是显示一个裸属性名。本文基于 CPython 源码与回归测试剖析这条消息从触发、存储到格式化的完整链路帮助你理解属性查找失败时错误信息的真实生成逻辑以及如何在自定义__getattr__、元类与模块级__getattr__中复现或控制这一行为。该变更记录在仓库的变更日志条目 Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst 中原文如下AttributeError: The default error message is now generated fromnameandobjattributes when both are set and the exception was constructed with no positional arguments, or with a single positional argument equal toname. Patch by Bartosz Sławecki.下面逐层展开这条变更背后的实现细节。一、变更的本质从裸属性名到完整句式的默认消息在属性查找失败时解释器会抛出一个AttributeError。过去当调用方以raise AttributeError(name)或直接raise AttributeError的方式构造异常时str(exc)只能展示一个孤零零的属性名或空字符串信息量有限。本次变更的核心规则即 NEWS 条目所述是只要满足以下两个条件解释器就用name和obj合成默认消息name与obj两个属性都被设置均为非空异常构造时没有传入任何位置参数或只传入了一个位置参数、且该参数恰好等于name此时可视为调用方只是把属性名带上了并没有提供真正的自定义消息。不满足上述条件时str(exc)仍然使用调用方显式提供的消息文本绝不覆盖自定义内容。二、数据承载PyAttributeErrorObject 与 name/obj 关键字参数AttributeError之所以能携带name和obj是因为其 C 级对象结构体在 Include/cpython/pyerrors.h 中扩展了两个字段typedef struct { PyException_HEAD PyObject *obj; PyObject *name; } PyAttributeErrorObject;从源码结构看PyException_HEAD保证了它首先是标准的异常对象含args等其上的obj指向属性查找失败时的目标对象name则是缺失的属性名必须是 Unicode 字符串。这两个字段通过AttributeError的__init__接受关键字参数注入。实现在 Objects/exceptions.c 的AttributeError_init中static int AttributeError_init(PyObject *op, PyObject *args, PyObject *kwds) { static char *kwlist[] {name, obj, NULL}; PyObject *name NULL; PyObject *obj NULL; if (BaseException_init(op, args, NULL) -1) { return -1; } PyObject *empty_tuple PyTuple_New(0); if (!empty_tuple) { return -1; } if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, |$OO:AttributeError, kwlist, name, obj)) { Py_DECREF(empty_tuple); return -1; } Py_DECREF(empty_tuple); PyAttributeErrorObject *self PyAttributeErrorObject_CAST(op); Py_XSETREF(self-name, Py_XNewRef(name)); Py_XSETREF(self-obj, Py_XNewRef(obj)); return 0; }注意这里的格式串|$OO:AttributeError$表示name与obj都是仅限关键字keyword-only参数位置参数则全部交给BaseException_init存入args元组。这一设计正是判断调用方是否提供了自定义消息的依据——args里放了什么直接决定了消息合成的分支走向。因此纯 Python 侧你可以这样构造class A: pass a A() exc AttributeError(namemissing, obja) print(exc.name) # missing print(exc.obj) # __main__.A object at 0x... print(str(exc)) # A object has no attribute missing三、消息合成AttributeError_str 的三分支格式化真正的默认消息合成逻辑位于 Objects/exceptions.c 的AttributeError_str。其判断流程如下if ( self-obj self-name PyUnicode_Check(self-name) ((PyTuple_GET_SIZE(self-args) 1 PyUnicode_Check(arg PyTuple_GET_ITEM(self-args, 0)) _PyUnicode_Equal(arg, self-name)) || PyTuple_GET_SIZE(self-args) 0) ) { obj Py_NewRef(self-obj); name Py_NewRef(self-name); }即触发合成的硬性前置条件有三条self-obj与self-name均非空且name是 Unicode 字符串args为空或args只有一个元素、该元素是 Unicode 且与name相等满足上述条件后解释器才读取obj与name进入格式化阶段。格式化阶段按obj的实际类型走三个分支同一文件中AttributeError_str的实现obj是模块PyModule_Check读取模块__dict__中的__name__输出module modname has no attribute attr若模块缺少__name__或__name__不是字符串则退化为更保守的module has no attribute attrobj是类型对象PyType_Check输出type object ClassName has no attribute attr其他普通对象输出ClassName object has no attribute attr%T会给出对象的类型限定名。if (PyModule_Check(obj)) { ... result PyUnicode_FromFormat(module %R has no attribute %R, modname, name); ... } else if (PyType_Check(obj)) { result PyUnicode_FromFormat(type object %N has no attribute %R, obj, name); } else { result PyUnicode_FromFormat(%T object has no attribute %R, obj, name); }这一分支设计正是为了让三类最常见的属性访问失败场景——访问模块成员、访问类属性、访问实例属性——都能输出贴近直觉的错误提示。四、触发时机属性查找失败时解释器自动填充 name/obj日常开发中我们并不会手动传name/obj这些字段是由解释器在属性查找失败时自动填充的。字节码求值循环中的LOAD_ATTR/LOAD_METHOD等指令在查找失败后会执行PyErr_SetObject(PyExc_AttributeError, name);该调用位于 Python/ceval.c。结合 Include/cpython/pyerrors.h 中PyAttributeErrorObject的字段定义可以推断解释器在此路径上会构造带name缺失的属性名与obj被访问的对象的AttributeError实例随后当用户捕获并打印该异常时AttributeError_str便会按上一节的三分支逻辑合成完整消息。同样的自动填充也发生在自定义钩子路径上当用户类实现__getattr__、元类实现__getattr__、模块实现__getattr__并在其中抛出AttributeError(name)或裸raise AttributeError时解释器会补齐obj从而使默认消息得以合成——这正是 Lib/test/test_exceptions.py 中一系列测试覆盖的场景。五、回归测试三类场景的行为契约仓库测试 Lib/test/test_exceptions.py 中AttributeErrorTests类完整定义了本次变更的行为契约可以当作规范文档来读测试方法抛出方式期望消息test_getattr_error_message对象__getattr__抛AttributeError(name)getattr(obj, missing1)module.Class object has no attribute missing1test_getattr_error_message裸raise AttributeErrorgetattr(obj, missing2)module.Class object has no attribute missing2test_getattr_error_message抛AttributeError(custom)getattr(obj, missing3)保持custom不合成test_class_getattr_error_message元类__getattr__getattr(cls, missing1)type object module.Class has no attribute missing1test_module_getattr_error_message模块__getattr__getattr(mod, missing1)module raisewithname has no attribute missing1test_module_getattr_error_message模块缺__name__或__name__非字符串getattr(mod, missing4)module has no attribute missing4退化分支其中test_getattr_has_name_and_objtest_exceptions.py还验证了普通属性访问失败时exc.name与exc.obj被正确填充test_attributestest_exceptions.py则确认了显式传参时字段的读写一致性。值得注意的细节是即使调用方抛出了自定义消息如AttributeError(custom)exc.name与exc.obj依然会被解释器填充见RaiseCustom用例断言只是str(exc)不采用合成文案——字段填充与消息合成是两个独立环节。六、消息生成与属性访问失败提示name suggestion的关系AttributeError的字符串表示是第一层信息当属性名拼写接近已有成员时CPython 还会在__getattr__失败路径上附加did you mean式的名字建议如A object has no attribute blech. Did you mean: blech_?。两者属于不同实现消息合成在Objects/exceptions.c的AttributeError_str中完成而名字建议由属性查找失败后的相近名字匹配逻辑产生相关测试集中在Lib/test/test_traceback.py测试注释name suggestion tests live in test_traceback。这意味着本次 NEWS 条目改进的是异常字符串的基础部分它让错误消息在无自定义文案时具备完整语义在此基础上再叠加拼写建议最终呈现给用户的是一条既完整又智能的报错。七、对开发者的实践意义自定义__getattr__时推荐直接raise AttributeError(name)或裸raise AttributeError让解释器借助obj自动生成完整、准确的消息若传入了与name不同的自定义文本则按你提供的文案展示——两种方式在 test_exceptions.py 中都有对应断言。捕获与调试时可从exc.name拿到缺失的属性名、从exc.obj拿到目标对象无需再解析字符串注意name与obj是仅限关键字参数$标记位置参数不会映射到它们。模块级__getattr__若模块__name__缺失或非字符串极端场景消息会退化为不带模块名的module has no attribute attr这是刻意的健壮性设计见 test_exceptions.py。内嵌/扩展开发者若在 C 扩展中手动构造AttributeError可仿照 Objects/exceptions.c 的方式通过name/obj关键字参数注入字段从而复用这一默认消息合成机制而PyErr_Format(PyExc_AttributeError, ...)直接抛出格式化文本的路径例如 Objects/descrobject.c 中的__get__/__set__检查则不会触发合成属于显式消息优先的典型用例。总结本次变更让AttributeError在没有自定义消息这一常见前提下自动产出包含对象类型与属性名的完整报错解释器在属性查找失败时填充name/objPython/ceval.c异常对象通过关键字参数承接字段Objects/exceptions.cAttributeError_str按模块/类型/普通对象三类模板合成消息Objects/exceptions.c并由 Lib/test/test_exceptions.py 的回归测试锁定行为。对使用者而言理解这条链路既能帮助你写出更友好的自定义异常也能在排查属性访问失败时快速定位exc.name与exc.obj这两个第一手线索。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考