Improved Error Messages
=======================
+* Improve the error suggestion for :exc:`NameError` exceptions for instances.
+ Now if a :exc:`NameError` is raised in a method and the instance has an
+ attribute that's exactly equal to the name in the exception, the suggestion
+ will include ``self.<NAME>`` instead of the closest match in the method
+ scope. Contributed by Pablo Galindo in :gh:`99139`.
+
+ >>> class A:
+ ... def __init__(self):
+ ... self.blech = 1
+ ...
+ ... def foo(self):
+ ... somethin = blech
+
+ >>> A().foo()
+ Traceback (most recent call last):
+ File "<stdin>", line 1
+ somethin = blech
+ ^^^^^
+ NameError: name 'blech' is not defined. Did you mean: 'self.blech'?
+
+
* Improve the :exc:`SyntaxError` error message when the user types ``import x
from y`` instead of ``from y import x``. Contributed by Pablo Galindo in :gh:`98931`.
STRUCT_FOR_ID(seek)
STRUCT_FOR_ID(seekable)
STRUCT_FOR_ID(selectors)
+ STRUCT_FOR_ID(self)
STRUCT_FOR_ID(send)
STRUCT_FOR_ID(sep)
STRUCT_FOR_ID(sequence)
INIT_ID(seek), \
INIT_ID(seekable), \
INIT_ID(selectors), \
+ INIT_ID(self), \
INIT_ID(send), \
INIT_ID(sep), \
INIT_ID(sequence), \
PyUnicode_InternInPlace(&string);
string = &_Py_ID(selectors);
PyUnicode_InternInPlace(&string);
+ string = &_Py_ID(self);
+ PyUnicode_InternInPlace(&string);
string = &_Py_ID(send);
PyUnicode_InternInPlace(&string);
string = &_Py_ID(sep);
_PyObject_Dump((PyObject *)&_Py_ID(selectors));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
};
+ if (Py_REFCNT((PyObject *)&_Py_ID(self)) < _PyObject_IMMORTAL_REFCNT) {
+ _PyObject_Dump((PyObject *)&_Py_ID(self));
+ Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
+ };
if (Py_REFCNT((PyObject *)&_Py_ID(send)) < _PyObject_IMMORTAL_REFCNT) {
_PyObject_Dump((PyObject *)&_Py_ID(send));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
actual = self.get_suggestion(func)
self.assertNotIn("blech", actual)
+
+ def test_name_error_with_instance(self):
+ class A:
+ def __init__(self):
+ self.blech = None
+ def foo(self):
+ blich = 1
+ x = blech
+
+ instance = A()
+ actual = self.get_suggestion(instance.foo)
+ self.assertIn("self.blech", actual)
+
+ def test_unbound_local_error_with_instance(self):
+ class A:
+ def __init__(self):
+ self.blech = None
+ def foo(self):
+ blich = 1
+ x = blech
+ blech = 1
+
+ instance = A()
+ actual = self.get_suggestion(instance.foo)
+ self.assertNotIn("self.blech", actual)
def test_unbound_local_error_does_not_match(self):
def func():
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
+
+ # Check first if we are in a method and the instance
+ # has the wrong name as attribute
+ if 'self' in frame.f_locals:
+ self = frame.f_locals['self']
+ if hasattr(self, wrong_name):
+ return f"self.{wrong_name}"
+
+ # Compute closest match
+
if len(d) > _MAX_CANDIDATE_ITEMS:
return None
wrong_name_len = len(wrong_name)
--- /dev/null
+Improve the error suggestion for :exc:`NameError` exceptions for instances.
+Now if a :exc:`NameError` is raised in a method and the instance has an
+attribute that's exactly equal to the name in the exception, the suggestion
+will include ``self.<NAME>`` instead of the closest match in the method
+scope. Patch by Pablo Galindo
#include "Python.h"
#include "pycore_frame.h"
+#include "pycore_runtime_init.h" // _Py_ID()
#include "pycore_pyerrors.h"
#include "pycore_code.h" // _PyCode_GetVarnames()
return NULL;
}
+ // Are we inside a method and the instance has an attribute called 'name'?
+ if (PySequence_Contains(dir, &_Py_ID(self)) > 0) {
+ PyObject* locals = PyFrame_GetLocals(frame);
+ if (!locals) {
+ goto error;
+ }
+ PyObject* self = PyDict_GetItem(locals, &_Py_ID(self)); /* borrowed */
+ Py_DECREF(locals);
+ if (!self) {
+ goto error;
+ }
+
+ if (PyObject_HasAttr(self, name)) {
+ Py_DECREF(dir);
+ return PyUnicode_FromFormat("self.%S", name);
+ }
+ }
+
PyObject *suggestions = calculate_suggestions(dir, name);
Py_DECREF(dir);
if (suggestions != NULL) {
Py_DECREF(dir);
return suggestions;
+
+error:
+ Py_DECREF(dir);
+ return NULL;
}
static bool