[Patches] Patch: AttributeError and NameError: second attempt.

Nick Mathewson nickm@MIT.EDU
Thu, 25 May 2000 23:19:36 -0400


Here's my latest attempt at improved error messages for NameError and
AttributeError in Python.  This revision incorporates a suggestion
independently made by Martin von Loewis and Mark Hammond: A NameError
object holds the frame and name that caused it, and an AttributeError
object holds the object and name that caused it.  For the main idea,
see the changes to exceptions.py below.

The biggest advantage of this approach is that it lets us delay the
process of generating an error message (which is O(n)) until we
actually need to print it.  This should overcome the possible 
technical objection Guido noted.

A secondary advantage (noted by Mr. von Loewis) is that putting the
error-generation logic in Python makes it easier to improve our search
for suggestions.  For example, a clever debugger (or a future version
of Python) might give messags like:
    * NameError: No such name as 'x'. Perhaps you meant 'self.x'?
    * NameError: No such name as 'peice'. Perhaps you meant 'piece'?

A tertiary advantage, noted by Mr. Hammond, is that storing objects in
AttributeError messages makes life easier for debugging and
development tools.

Other improvements:
	* We no longer require strcasecmp.
	* Most -- possibly all -- of the memory leaks are gone.
	* It's a bit slower if you print the error, but a lot faster
          if you don't.

The main disagvantages of this approach are:
	* AttributeError objects have references to their objects.
          This means that under certain really messed-up conditions,
          this can create reference cycles in exiting code.  You'd
          need to do something like:
               def f(self):
                   try:
                      self.nonexistent
                   except AttributeError, e:
                      self.saved_exception = e
        * Added complexity.

===============
Description:

Added functionality to __str__ methods of NameError and AttributeError:
We suggest alternative names which differ only in case, if such names
exist.  Also added convience functions to generate new AttributeError
objects.

===============
Disclaimer:

I confirm that, to the best of my knowledge and belief, this
contribution is free of any claims of third parties under
copyright, patent or other rights or interests ("claims").  To
the extent that I have any such claims, I hereby grant to CNRI a
nonexclusive, irrevocable, royalty-free, worldwide license to
reproduce, distribute, perform and/or display publicly, prepare
derivative versions, and otherwise use this contribution as part
of the Python software and its related documentation, or any
derivative versions thereof, at no cost to CNRI or its licensed
users, and to authorize others to do so.

I acknowledge that CNRI may, at its sole discretion, decide
whether or not to incorporate this contribution in the Python
software and its related documentation.  I further grant CNRI
permission to use my name and other identifying information
provided to CNRI by me for use in connection with the Python
software and its related documentation.

NOTE: THIS PATCH IS AGAINST VERSION 1.6A2.
========================================
diff -cNr Python-1.6a2/Include/pyerrors.h casepy/Include/pyerrors.h
*** Python-1.6a2/Include/pyerrors.h	Fri Mar 10 17:33:32 2000
--- casepy/Include/pyerrors.h	Thu May 25 19:11:15 2000
***************
*** 96,101 ****
--- 96,103 ----
  extern DL_IMPORT(PyObject *) PyErr_SetFromErrno Py_PROTO((PyObject *));
  extern DL_IMPORT(PyObject *) PyErr_SetFromErrnoWithFilename Py_PROTO((PyObject *, char *));
  extern DL_IMPORT(PyObject *) PyErr_Format Py_PROTO((PyObject *, const char *, ...));
+ extern DL_IMPORT(void) PyErr_AttributeError Py_PROTO((PyObject *, PyObject *));
+ extern DL_IMPORT(void) PyErr_AttributeErrorString Py_PROTO((PyObject *, char *));
  #ifdef MS_WINDOWS
  extern DL_IMPORT(PyObject *) PyErr_SetFromWindowsErrWithFilename(int, const char *);
  extern DL_IMPORT(PyObject *) PyErr_SetFromWindowsErr(int);
*** Python-1.6a2/Lib/exceptions.py	Fri Mar 10 18:16:02 2000
--- casepy/Lib/exceptions.py	Thu May 25 23:09:16 2000
***************
*** 217,227 ****
  
  class AttributeError(StandardError):
      """Attribute not found."""
!     pass
  
  class NameError(StandardError):
      """Name not found globally."""
!     pass
  
  class UnboundLocalError(NameError):
      """Local name referenced but not bound to a value."""
--- 217,263 ----
  
  class AttributeError(StandardError):
      """Attribute not found."""
! 
!     def __str__(self):
!         if len(self.args) == 2:
!             obj  = self.args[0]
!             name = self.args[1]
!             if hasattr(obj, '__class__'):
!                 typename = "'%s' instance" % obj.__class__.__name__
!             else:
!                 typename = type(obj).__name__
!             nearest = _find_nearest_attribute(obj, name)
!             if nearest: 
!                 s = ("%s has no attribute '%s'. Perhaps you meant '%s'?"
!                      % (typename, name, nearest))
!             else:
!                 s = "%s has no attribute '%s'" % (typename, name)
!             self.args = (s,)
!             return s
!         elif len(self.args) == 1:
!             return str(self.args[0])
!         else:
!             return ""
  
  class NameError(StandardError):
      """Name not found globally."""
! 
!     def __str__(self):
!         if len(self.args) == 2:
!             frame = self.args[0]
!             name  = self.args[1]
!             nearest = _find_nearest_name_in_frame(frame, name)
!             if nearest: 
!                 s = ("No such name as '%s'. Perhaps you meant '%s'?" 
!                      % (name,nearest))
!             else:
!                 s = ("No such name as '%s'" % name)
!             self.args = (s,)
!             return s
!         elif len(self.args) == 1:
!             return str(self.args[0])
!         else:
!             return ""
  
  class UnboundLocalError(NameError):
      """Local name referenced but not bound to a value."""
***************
*** 245,247 ****
--- 281,329 ----
              self.code = args[0]
          else:
              self.code = args
+ 
+ def _nearest_name_in_list(lst, name):
+     """Returns the element of 'lst' which is closest to name.  Name must
+        be in lowercase.  Returns None if no such element is found."""
+ 
+     for i in lst:
+         if i.lower() == name:
+             return i
+ 
+     return None
+ 
+ def _find_nearest_attribute(object, name):
+     """Returns the attribute on object which is closest to name.
+        Returns None if no such attribute is found."""
+ 
+     name = name.lower()
+     if hasattr(object, '__dict__'):
+         r = _nearest_name_in_list(object.__dict__.keys(), name)
+         if r:
+             return r
+     if hasattr(object, '__class__') and hasattr(object.__class__, '__dict__'):
+         r = _nearest_name_in_list(object.__class__.__dict__.keys(), name)
+         if r:
+             return r
+     if hasattr(object, '__members__'):
+         r = _nearest_name_in_list(object.__members__, name)
+         if r: 
+             return r
+     if hasattr(object, '__methods__'):
+         r = _nearest_name_in_list(object.__members__, name)
+         if r: 
+             return r
+ 
+     return None
+ 
+ def _find_nearest_name_in_frame(frame, name):
+     name = name.lower()
+     
+     for attr in ('f_locals', 'f_globals', 'f_builtins'):
+         if hasattr(frame, attr):
+             r = _nearest_name_in_list(getattr(frame,attr).keys(), name)
+             if r:
+                 return r
+ 
+     return None
diff -cNr Python-1.6a2/Objects/abstract.c casepy/Objects/abstract.c
*** Python-1.6a2/Objects/abstract.c	Wed Apr  5 16:11:20 2000
--- casepy/Objects/abstract.c	Thu May 25 19:26:43 2000
***************
*** 1536,1542 ****
  	func = PyObject_GetAttrString(o, name);
  	if (func == NULL) {
  		va_end(va);
! 		PyErr_SetString(PyExc_AttributeError, name);
  		return 0;
  	}
  
--- 1536,1542 ----
  	func = PyObject_GetAttrString(o, name);
  	if (func == NULL) {
  		va_end(va);
! 		PyErr_AttributeErrorString(o, name);
  		return 0;
  	}
  
diff -cNr Python-1.6a2/Objects/classobject.c casepy/Objects/classobject.c
*** Python-1.6a2/Objects/classobject.c	Mon Apr 10 09:03:19 2000
--- casepy/Objects/classobject.c	Thu May 25 18:50:44 2000
***************
*** 206,212 ****
  	}
  	v = class_lookup(op, name, &class);
  	if (v == NULL) {
! 		PyErr_SetObject(PyExc_AttributeError, name);
  		return NULL;
  	}
  	Py_INCREF(v);
--- 206,212 ----
  	}
  	v = class_lookup(op, name, &class);
  	if (v == NULL) {
! 		PyErr_AttributeError((PyObject*) op, name);
  		return NULL;
  	}
  	Py_INCREF(v);
***************
*** 330,337 ****
  	if (v == NULL) {
  		int rv = PyDict_DelItem(op->cl_dict, name);
  		if (rv < 0)
! 			PyErr_SetString(PyExc_AttributeError,
! 				   "delete non-existing class attribute");
  		return rv;
  	}
  	else
--- 330,336 ----
  	if (v == NULL) {
  		int rv = PyDict_DelItem(op->cl_dict, name);
  		if (rv < 0)
! 			PyErr_AttributeError((PyObject*) op, name);
  		return rv;
  	}
  	else
***************
*** 592,601 ****
  	if (v == NULL) {
  		v = class_lookup(inst->in_class, name, &class);
  		if (v == NULL) {
! 			PyErr_Format(PyExc_AttributeError,
! 				     "'%.50s' instance has no attribute '%.400s'",
! 				     PyString_AsString(inst->in_class->cl_name),
! 				     sname);
  			return NULL;
  		}
  	}
--- 591,597 ----
  	if (v == NULL) {
  		v = class_lookup(inst->in_class, name, &class);
  		if (v == NULL) {
! 			PyErr_AttributeError((PyObject*) inst, name);
  			return NULL;
  		}
  	}
***************
*** 650,657 ****
  	if (v == NULL) {
  		int rv = PyDict_DelItem(inst->in_dict, name);
  		if (rv < 0)
! 			PyErr_SetString(PyExc_AttributeError,
! 				   "delete non-existing instance attribute");
  		return rv;
  	}
  	else
--- 646,652 ----
  	if (v == NULL) {
  		int rv = PyDict_DelItem(inst->in_dict, name);
  		if (rv < 0)
! 			PyErr_AttributeError((PyObject*) inst, name);
  		return rv;
  	}
  	else
diff -cNr Python-1.6a2/Objects/methodobject.c casepy/Objects/methodobject.c
*** Python-1.6a2/Objects/methodobject.c	Thu Jan 20 17:32:54 2000
--- casepy/Objects/methodobject.c	Thu May 25 19:27:00 2000
***************
*** 136,142 ****
  		return Py_BuildValue("[sss]",
  				     "__doc__", "__name__", "__self__");
  	}
! 	PyErr_SetString(PyExc_AttributeError, name);
  	return NULL;
  }
  
--- 136,142 ----
  		return Py_BuildValue("[sss]",
  				     "__doc__", "__name__", "__self__");
  	}
! 	PyErr_AttributeErrorString((PyObject*) m, name);
  	return NULL;
  }
  
diff -cNr Python-1.6a2/Objects/moduleobject.c casepy/Objects/moduleobject.c
*** Python-1.6a2/Objects/moduleobject.c	Mon Feb 15 09:47:16 1999
--- casepy/Objects/moduleobject.c	Thu May 25 19:10:59 2000
***************
*** 207,214 ****
  		return m->md_dict;
  	}
  	res = PyDict_GetItemString(m->md_dict, name);
! 	if (res == NULL)
! 		PyErr_SetString(PyExc_AttributeError, name);
  	else
  		Py_INCREF(res);
  	return res;
--- 207,214 ----
  		return m->md_dict;
  	}
  	res = PyDict_GetItemString(m->md_dict, name);
! 	if (res == NULL) 
! 		PyErr_AttributeErrorString((PyObject*) m, name);
  	else
  		Py_INCREF(res);
  	return res;
diff -cNr Python-1.6a2/Objects/object.c casepy/Objects/object.c
*** Python-1.6a2/Objects/object.c	Mon Apr 10 09:42:33 2000
--- casepy/Objects/object.c	Thu May 25 19:27:36 2000
***************
*** 404,413 ****
  	}
  
  	if (v->ob_type->tp_getattr == NULL) {
! 		PyErr_Format(PyExc_AttributeError,
! 			     "'%.50s' object has no attribute '%.400s'",
! 			     v->ob_type->tp_name,
! 			     name);
  		return NULL;
  	}
  	else {
--- 404,410 ----
  	}
  
  	if (v->ob_type->tp_getattr == NULL) {
! 		PyErr_AttributeErrorString(v, name);
  		return NULL;
  	}
  	else {
diff -cNr Python-1.6a2/Objects/sliceobject.c casepy/Objects/sliceobject.c
*** Python-1.6a2/Objects/sliceobject.c	Fri Oct 11 12:25:09 1996
--- casepy/Objects/sliceobject.c	Thu May 25 19:25:46 2000
***************
*** 158,164 ****
  				     "start", "stop", "step");
  	}
  	else {
! 		PyErr_SetString(PyExc_AttributeError, name);
  		return NULL;
  	}
  	Py_INCREF(ret);
--- 158,164 ----
  				     "start", "stop", "step");
  	}
  	else {
! 		PyErr_AttributeErrorString((PyObject*)self, name);
  		return NULL;
  	}
  	Py_INCREF(ret);
diff -cNr Python-1.6a2/Objects/typeobject.c casepy/Objects/typeobject.c
*** Python-1.6a2/Objects/typeobject.c	Mon Jun  2 10:43:07 1997
--- casepy/Objects/typeobject.c	Thu May 25 18:54:21 2000
***************
*** 51,57 ****
  	}
  	if (strcmp(name, "__members__") == 0)
  		return Py_BuildValue("[ss]", "__doc__", "__name__");
! 	PyErr_SetString(PyExc_AttributeError, name);
  	return NULL;
  }
  
--- 51,57 ----
  	}
  	if (strcmp(name, "__members__") == 0)
  		return Py_BuildValue("[ss]", "__doc__", "__name__");
! 	PyErr_AttributeError((PyObject*) t, name);
  	return NULL;
  }
  
diff -cNr Python-1.6a2/Python/ceval.c casepy/Python/ceval.c
*** Python-1.6a2/Python/ceval.c	Mon Apr 10 08:45:10 2000
--- casepy/Python/ceval.c	Thu May 25 22:15:20 2000
***************
*** 93,99 ****
  static void set_exc_info Py_PROTO((PyThreadState *,
  				PyObject *, PyObject *, PyObject *));
  static void reset_exc_info Py_PROTO((PyThreadState *));
! 
  
  /* Dynamic execution profile */
  #ifdef DYNAMIC_EXECUTION_PROFILE
--- 93,99 ----
  static void set_exc_info Py_PROTO((PyThreadState *,
  				PyObject *, PyObject *, PyObject *));
  static void reset_exc_info Py_PROTO((PyThreadState *));
! static void set_name_error Py_PROTO((PyFrameObject *, PyObject *));
  
  /* Dynamic execution profile */
  #ifdef DYNAMIC_EXECUTION_PROFILE
***************
*** 1195,1201 ****
  				break;
  			}
  			if ((err = PyDict_DelItem(x, w)) != 0)
! 				PyErr_SetObject(PyExc_NameError, w);
  			break;
  
  #ifdef CASE_TOO_BIG
--- 1195,1201 ----
  				break;
  			}
  			if ((err = PyDict_DelItem(x, w)) != 0)
! 				set_name_error(f,w);
  			break;
  
  #ifdef CASE_TOO_BIG
***************
*** 1275,1281 ****
  		case DELETE_GLOBAL:
  			w = GETNAMEV(oparg);
  			if ((err = PyDict_DelItem(f->f_globals, w)) != 0)
! 				PyErr_SetObject(PyExc_NameError, w);
  			break;
  		
  		case LOAD_CONST:
--- 1275,1281 ----
  		case DELETE_GLOBAL:
  			w = GETNAMEV(oparg);
  			if ((err = PyDict_DelItem(f->f_globals, w)) != 0)
! 				set_name_error(f,w);
  			break;
  		
  		case LOAD_CONST:
***************
*** 1297,1304 ****
  				if (x == NULL) {
  					x = PyDict_GetItem(f->f_builtins, w);
  					if (x == NULL) {
! 						PyErr_SetObject(
! 							PyExc_NameError, w);
  						break;
  					}
  				}
--- 1297,1303 ----
  				if (x == NULL) {
  					x = PyDict_GetItem(f->f_builtins, w);
  					if (x == NULL) {
! 						set_name_error(f,w);
  						break;
  					}
  				}
***************
*** 1313,1319 ****
  			if (x == NULL) {
  				x = PyDict_GetItem(f->f_builtins, w);
  				if (x == NULL) {
! 					PyErr_SetObject(PyExc_NameError, w);
  					break;
  				}
  			}
--- 1312,1318 ----
  			if (x == NULL) {
  				x = PyDict_GetItem(f->f_builtins, w);
  				if (x == NULL) {
! 					set_name_error(f,w);
  					break;
  				}
  			}
***************
*** 2925,2930 ****
--- 2924,2946 ----
  	return list;
  }
  
+ static void
+ set_name_error(f, name) 
+         PyFrameObject *f; 
+         PyObject *name;
+ {
+ 	PyObject *tuple = PyTuple_New(2);
+ 
+ 	PyTuple_SET_ITEM(tuple, 0, (PyObject*) f);
+ 	PyTuple_SET_ITEM(tuple, 1, name);
+ 
+ 	Py_INCREF(f);
+ 	Py_INCREF(name);
+ 
+ 	PyErr_SetObject(PyExc_NameError, tuple);
+ 
+ 	Py_DECREF(tuple);
+ }     
  
  #ifdef DYNAMIC_EXECUTION_PROFILE
  
diff -cNr Python-1.6a2/Python/errors.c casepy/Python/errors.c
*** Python-1.6a2/Python/errors.c	Thu Mar  2 08:55:01 2000
--- casepy/Python/errors.c	Thu May 25 22:15:53 2000
***************
*** 353,358 ****
--- 353,382 ----
  	return PyErr_SetFromErrnoWithFilename(exc, NULL);
  }
  
+ void
+ PyErr_AttributeError(object, name)
+         PyObject *object;
+         PyObject *name;
+ {
+ 	PyObject *tuple = PyTuple_New(2);
+ 	Py_INCREF(object);
+ 	Py_INCREF(name);
+ 	PyTuple_SET_ITEM(tuple, 0, object);
+ 	PyTuple_SET_ITEM(tuple, 1, name);
+ 	PyErr_SetObject(PyExc_AttributeError, tuple);
+ 	Py_DECREF(tuple);
+ }
+ 
+ void
+ PyErr_AttributeErrorString(object, name)
+         PyObject *object;
+         char *name;
+ {
+ 	PyObject *str = PyString_FromString(name);
+ 	PyErr_AttributeError(object, str);
+ 	Py_DECREF(str);
+ }
+ 
  #ifdef MS_WINDOWS 
  /* Windows specific error code handling */
  PyObject *PyErr_SetFromWindowsErrWithFilename(
========================================

So, is this better or worse? 

-- 
Nick Mathewson     <nickm@alum.mit.edu>     http://www.mit.edu/~nickm/