Commit bf36409e authored by Brett Cannon's avatar Brett Cannon

PEP 352 implementation. Creates a new base class, BaseException, which has an

added message attribute compared to the previous version of Exception.  It is
also a new-style class, making all exceptions now new-style.  KeyboardInterrupt
and SystemExit inherit from BaseException directly.  String exceptions now
raise DeprecationWarning.

Applies patch 1104669, and closes bugs 1012952 and 518846.
parent 76246747
......@@ -26,9 +26,32 @@ PyAPI_FUNC(int) PyErr_GivenExceptionMatches(PyObject *, PyObject *);
PyAPI_FUNC(int) PyErr_ExceptionMatches(PyObject *);
PyAPI_FUNC(void) PyErr_NormalizeException(PyObject**, PyObject**, PyObject**);
/* */
#define PyExceptionClass_Check(x) \
(PyClass_Check((x)) \
|| (PyType_Check((x)) && PyType_IsSubtype( \
(PyTypeObject*)(x), (PyTypeObject*)PyExc_BaseException)))
#define PyExceptionInstance_Check(x) \
(PyInstance_Check((x)) || \
(PyType_IsSubtype((x)->ob_type, (PyTypeObject*)PyExc_BaseException)))
#define PyExceptionClass_Name(x) \
(PyClass_Check((x)) \
? PyString_AS_STRING(((PyClassObject*)(x))->cl_name) \
: (char *)(((PyTypeObject*)(x))->tp_name))
#define PyExceptionInstance_Class(x) \
((PyInstance_Check((x)) \
? (PyObject*)((PyInstanceObject*)(x))->in_class \
: (PyObject*)((x)->ob_type)))
/* Predefined exceptions */
PyAPI_DATA(PyObject *) PyExc_BaseException;
PyAPI_DATA(PyObject *) PyExc_Exception;
PyAPI_DATA(PyObject *) PyExc_StopIteration;
PyAPI_DATA(PyObject *) PyExc_GeneratorExit;
......
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- Exception
+-- GeneratorExit
+-- StopIteration
+-- StandardError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| | +-- UnicodeError
| | +-- UnicodeDecodeError
| | +-- UnicodeEncodeError
| | +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- OverflowWarning [not generated by the interpreter]
......@@ -488,12 +488,12 @@ INFO:a.b.c.d:Info 5
-- log_test4 begin ---------------------------------------------------
config0: ok.
config1: ok.
config2: exceptions.AttributeError
config3: exceptions.KeyError
config2: <class 'exceptions.AttributeError'>
config3: <class 'exceptions.KeyError'>
-- log_test4 end ---------------------------------------------------
-- log_test5 begin ---------------------------------------------------
ERROR:root:just testing
exceptions.KeyError... Don't panic!
<class 'exceptions.KeyError'>... Don't panic!
-- log_test5 end ---------------------------------------------------
-- logrecv output begin ---------------------------------------------------
ERR -> CRITICAL: Message 0 (via logrecv.tcp.ERR)
......
......@@ -96,7 +96,7 @@ def do_infix_binops():
x = eval('a %s b' % op)
except:
error = sys.exc_info()[:2]
print '... %s' % error[0]
print '... %s.%s' % (error[0].__module__, error[0].__name__)
else:
print '=', format_result(x)
try:
......@@ -108,7 +108,7 @@ def do_infix_binops():
exec('z %s= b' % op)
except:
error = sys.exc_info()[:2]
print '... %s' % error[0]
print '... %s.%s' % (error[0].__module__, error[0].__name__)
else:
print '=>', format_result(z)
......@@ -121,7 +121,7 @@ def do_prefix_binops():
x = eval('%s(a, b)' % op)
except:
error = sys.exc_info()[:2]
print '... %s' % error[0]
print '... %s.%s' % (error[0].__module__, error[0].__name__)
else:
print '=', format_result(x)
......
......@@ -3355,31 +3355,6 @@ def docdescriptor():
vereq(NewClass.__doc__, 'object=None; type=NewClass')
vereq(NewClass().__doc__, 'object=NewClass instance; type=NewClass')
def string_exceptions():
if verbose:
print "Testing string exceptions ..."
# Ensure builtin strings work OK as exceptions.
astring = "An exception string."
try:
raise astring
except astring:
pass
else:
raise TestFailed, "builtin string not usable as exception"
# Ensure string subclass instances do not.
class MyStr(str):
pass
newstring = MyStr("oops -- shouldn't work")
try:
raise newstring
except TypeError:
pass
except:
raise TestFailed, "string subclass allowed as exception"
def copy_setstate():
if verbose:
print "Testing that copy.*copy() correctly uses __setstate__..."
......@@ -4172,7 +4147,6 @@ def test_main():
funnynew()
imulbug()
docdescriptor()
string_exceptions()
copy_setstate()
slices()
subtype_resurrection()
......
......@@ -29,10 +29,7 @@ def test_raise_catch(exc):
def r(thing):
test_raise_catch(thing)
if isinstance(thing, ClassType):
print thing.__name__
else:
print thing
print getattr(thing, '__name__', thing)
r(AttributeError)
import sys
......
import unittest
import __builtin__
import exceptions
import warnings
from test.test_support import run_unittest
import os
from platform import system as platform_system
class ExceptionClassTests(unittest.TestCase):
"""Tests for anything relating to exception objects themselves (e.g.,
inheritance hierarchy)"""
def test_builtins_new_style(self):
self.failUnless(issubclass(Exception, object))
def verify_instance_interface(self, ins):
for attr in ("args", "message", "__str__", "__unicode__", "__repr__",
"__getitem__"):
self.failUnless(hasattr(ins, attr), "%s missing %s attribute" %
(ins.__class__.__name__, attr))
def test_inheritance(self):
# Make sure the inheritance hierarchy matches the documentation
exc_set = set(x for x in dir(exceptions) if not x.startswith('_'))
inheritance_tree = open(os.path.join(os.path.split(__file__)[0],
'exception_hierarchy.txt'))
try:
superclass_name = inheritance_tree.readline().rstrip()
try:
last_exc = getattr(__builtin__, superclass_name)
except AttributeError:
self.fail("base class %s not a built-in" % superclass_name)
self.failUnless(superclass_name in exc_set)
exc_set.discard(superclass_name)
superclasses = [] # Loop will insert base exception
last_depth = 0
for exc_line in inheritance_tree:
exc_line = exc_line.rstrip()
depth = exc_line.rindex('-')
exc_name = exc_line[depth+2:] # Slice past space
if '(' in exc_name:
paren_index = exc_name.index('(')
platform_name = exc_name[paren_index+1:-1]
if platform_system() != platform_name:
exc_set.discard(exc_name)
continue
if '[' in exc_name:
left_bracket = exc_name.index('[')
exc_name = exc_name[:left_bracket-1] # cover space
try:
exc = getattr(__builtin__, exc_name)
except AttributeError:
self.fail("%s not a built-in exception" % exc_name)
if last_depth < depth:
superclasses.append((last_depth, last_exc))
elif last_depth > depth:
while superclasses[-1][0] >= depth:
superclasses.pop()
self.failUnless(issubclass(exc, superclasses[-1][1]),
"%s is not a subclass of %s" % (exc.__name__,
superclasses[-1][1].__name__))
try: # Some exceptions require arguments; just skip them
self.verify_instance_interface(exc())
except TypeError:
pass
self.failUnless(exc_name in exc_set)
exc_set.discard(exc_name)
last_exc = exc
last_depth = depth
finally:
inheritance_tree.close()
self.failUnlessEqual(len(exc_set), 0, "%s not accounted for" % exc_set)
interface_tests = ("length", "args", "message", "str", "unicode", "repr",
"indexing")
def interface_test_driver(self, results):
for test_name, (given, expected) in zip(self.interface_tests, results):
self.failUnlessEqual(given, expected, "%s: %s != %s" % (test_name,
given, expected))
def test_interface_single_arg(self):
# Make sure interface works properly when given a single argument
arg = "spam"
exc = Exception(arg)
results = ([len(exc.args), 1], [exc.args[0], arg], [exc.message, arg],
[str(exc), str(arg)], [unicode(exc), unicode(arg)],
[repr(exc), exc.__class__.__name__ + repr(exc.args)], [exc[0], arg])
self.interface_test_driver(results)
def test_interface_multi_arg(self):
# Make sure interface correct when multiple arguments given
arg_count = 3
args = tuple(range(arg_count))
exc = Exception(*args)
results = ([len(exc.args), arg_count], [exc.args, args],
[exc.message, ''], [str(exc), str(args)],
[unicode(exc), unicode(args)],
[repr(exc), exc.__class__.__name__ + repr(exc.args)],
[exc[-1], args[-1]])
self.interface_test_driver(results)
def test_interface_no_arg(self):
# Make sure that with no args that interface is correct
exc = Exception()
results = ([len(exc.args), 0], [exc.args, tuple()], [exc.message, ''],
[str(exc), ''], [unicode(exc), u''],
[repr(exc), exc.__class__.__name__ + '()'], [True, True])
self.interface_test_driver(results)
class UsageTests(unittest.TestCase):
"""Test usage of exceptions"""
def setUp(self):
self._filters = warnings.filters[:]
def tearDown(self):
warnings.filters = self._filters[:]
def test_raise_classic(self):
class ClassicClass:
pass
try:
raise ClassicClass
except ClassicClass:
pass
except:
self.fail("unable to raise classic class")
try:
raise ClassicClass()
except ClassicClass:
pass
except:
self.fail("unable to raise class class instance")
def test_raise_new_style_non_exception(self):
class NewStyleClass(object):
pass
try:
raise NewStyleClass
except TypeError:
pass
except:
self.fail("unable to raise new-style class")
try:
raise NewStyleClass()
except TypeError:
pass
except:
self.fail("unable to raise new-style class instance")
def test_raise_string(self):
warnings.resetwarnings()
warnings.filterwarnings("error")
try:
raise "spam"
except DeprecationWarning:
pass
except:
self.fail("raising a string did not cause a DeprecationWarning")
def test_catch_string(self):
# Test will be pertinent when catching exceptions raises a
# DeprecationWarning
warnings.filterwarnings("ignore", "raising")
str_exc = "spam"
try:
raise str_exc
except str_exc:
pass
except:
self.fail("catching a string exception failed")
def test_main():
run_unittest(ExceptionClassTests, UsageTests)
if __name__ == '__main__':
test_main()
......@@ -157,7 +157,8 @@ def format_exception_only(etype, value):
which exception occurred is the always last string in the list.
"""
list = []
if type(etype) == types.ClassType:
if (type(etype) == types.ClassType
or (isinstance(etype, type) and issubclass(etype, Exception))):
stype = etype.__name__
else:
stype = etype
......
......@@ -145,7 +145,8 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
assert action in ("error", "ignore", "always", "default", "module",
"once"), "invalid action: %r" % (action,)
assert isinstance(message, basestring), "message must be a string"
assert isinstance(category, types.ClassType), "category must be a class"
assert isinstance(category, (type, types.ClassType)), \
"category must be a class"
assert issubclass(category, Warning), "category must be a Warning subclass"
assert isinstance(module, basestring), "module must be a string"
assert isinstance(lineno, int) and lineno >= 0, \
......
......@@ -12,6 +12,11 @@ What's New in Python 2.5 alpha 1?
Core and builtins
-----------------
- PEP 352, patch #1104669: Make exceptions new-style objects. Introduced the
new exception base class, BaseException, which has a new message attribute.
KeyboardInterrupt and SystemExit to directly inherit from BaseException now.
Raising a string exception now raises a DeprecationWarning.
- Patch #1438387, PEP 328: relative and absolute imports. Imports can now be
explicitly relative, using 'from .module import name' to mean 'from the same
package as this module is in. Imports without dots still default to the
......
......@@ -230,11 +230,11 @@ gen_throw(PyGenObject *gen, PyObject *args)
Py_XINCREF(val);
Py_XINCREF(tb);
if (PyClass_Check(typ)) {
if (PyExceptionClass_Check(typ)) {
PyErr_NormalizeException(&typ, &val, &tb);
}
else if (PyInstance_Check(typ)) {
else if (PyExceptionInstance_Check(typ)) {
/* Raising an instance. The value should be a dummy. */
if (val && val != Py_None) {
PyErr_SetString(PyExc_TypeError,
......@@ -245,7 +245,7 @@ gen_throw(PyGenObject *gen, PyObject *args)
/* Normalize to raise <class>, <instance> */
Py_XDECREF(val);
val = typ;
typ = (PyObject*) ((PyInstanceObject*)typ)->in_class;
typ = PyExceptionInstance_Class(typ);
Py_INCREF(typ);
}
}
......
......@@ -1685,7 +1685,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
why == WHY_CONTINUE)
retval = POP();
}
else if (PyClass_Check(v) || PyString_Check(v)) {
else if (PyExceptionClass_Check(v) || PyString_Check(v)) {
w = POP();
u = POP();
PyErr_Restore(v, w, u);
......@@ -3026,14 +3026,14 @@ do_raise(PyObject *type, PyObject *value, PyObject *tb)
/* Raising builtin string is deprecated but still allowed --
* do nothing. Raising an instance of a new-style str
* subclass is right out. */
if (-1 == PyErr_Warn(PyExc_PendingDeprecationWarning,
if (PyErr_Warn(PyExc_DeprecationWarning,
"raising a string exception is deprecated"))
goto raise_error;
}
else if (PyClass_Check(type))
else if (PyExceptionClass_Check(type))
PyErr_NormalizeException(&type, &value, &tb);
else if (PyInstance_Check(type)) {
else if (PyExceptionInstance_Check(type)) {
/* Raising an instance. The value should be a dummy. */
if (value != Py_None) {
PyErr_SetString(PyExc_TypeError,
......@@ -3044,7 +3044,7 @@ do_raise(PyObject *type, PyObject *value, PyObject *tb)
/* Normalize to raise <class>, <instance> */
Py_DECREF(value);
value = type;
type = (PyObject*) ((PyInstanceObject*)type)->in_class;
type = PyExceptionInstance_Class(type);
Py_INCREF(type);
}
}
......
......@@ -448,9 +448,8 @@ static void wrong_exception_type(PyObject *exc)
PyObject *PyCodec_StrictErrors(PyObject *exc)
{
if (PyInstance_Check(exc))
PyErr_SetObject((PyObject*)((PyInstanceObject*)exc)->in_class,
exc);
if (PyExceptionInstance_Check(exc))
PyErr_SetObject(PyExceptionInstance_Class(exc), exc);
else
PyErr_SetString(PyExc_TypeError, "codec must pass exception instance");
return NULL;
......
......@@ -97,11 +97,14 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
return 0;
}
/* err might be an instance, so check its class. */
if (PyInstance_Check(err))
err = (PyObject*)((PyInstanceObject*)err)->in_class;
if (PyExceptionInstance_Check(err))
err = PyExceptionInstance_Class(err);
if (PyClass_Check(err) && PyClass_Check(exc))
return PyClass_IsSubclass(err, exc);
if (PyExceptionClass_Check(err) && PyExceptionClass_Check(exc)) {
/* problems here!? not sure PyObject_IsSubclass expects to
be called with an exception pending... */
return PyObject_IsSubclass(err, exc);
}
return err == exc;
}
......@@ -138,19 +141,19 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
Py_INCREF(value);
}
if (PyInstance_Check(value))
inclass = (PyObject*)((PyInstanceObject*)value)->in_class;
if (PyExceptionInstance_Check(value))
inclass = PyExceptionInstance_Class(value);
/* Normalize the exception so that if the type is a class, the
value will be an instance.
*/
if (PyClass_Check(type)) {
if (PyExceptionClass_Check(type)) {
/* if the value was not an instance, or is not an instance
whose class is (or is derived from) type, then use the
value as an argument to instantiation of the type
class.
*/
if (!inclass || !PyClass_IsSubclass(inclass, type)) {
if (!inclass || !PyObject_IsSubclass(inclass, type)) {
PyObject *args, *res;
if (value == Py_None)
......@@ -282,7 +285,7 @@ PyErr_SetFromErrnoWithFilenameObject(PyObject *exc, PyObject *filenameObject)
{
/* Note that the Win32 errors do not lineup with the
errno error. So if the error is in the MSVC error
table, we use it, otherwise we assume it really _is_
table, we use it, otherwise we assume it really _is_
a Win32 error code
*/
if (i > 0 && i < _sys_nerr) {
......@@ -302,7 +305,7 @@ PyErr_SetFromErrnoWithFilenameObject(PyObject *exc, PyObject *filenameObject)
0, /* size not used */
NULL); /* no args */
if (len==0) {
/* Only ever seen this in out-of-mem
/* Only ever seen this in out-of-mem
situations */
sprintf(s_small_buf, "Windows Error 0x%X", i);
s = s_small_buf;
......@@ -345,8 +348,8 @@ PyErr_SetFromErrnoWithFilename(PyObject *exc, char *filename)
PyObject *
PyErr_SetFromErrnoWithUnicodeFilename(PyObject *exc, Py_UNICODE *filename)
{
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
NULL;
PyObject *result = PyErr_SetFromErrnoWithFilenameObject(exc, name);
Py_XDECREF(name);
......@@ -360,7 +363,7 @@ PyErr_SetFromErrno(PyObject *exc)
return PyErr_SetFromErrnoWithFilenameObject(exc, NULL);
}
#ifdef MS_WINDOWS
#ifdef MS_WINDOWS
/* Windows specific error code handling */
PyObject *PyErr_SetExcFromWindowsErrWithFilenameObject(
PyObject *exc,
......@@ -415,8 +418,8 @@ PyObject *PyErr_SetExcFromWindowsErrWithFilename(
const char *filename)
{
PyObject *name = filename ? PyString_FromString(filename) : NULL;
PyObject *ret = PyErr_SetExcFromWindowsErrWithFilenameObject(exc,
ierr,
PyObject *ret = PyErr_SetExcFromWindowsErrWithFilenameObject(exc,
ierr,
name);
Py_XDECREF(name);
return ret;
......@@ -428,11 +431,11 @@ PyObject *PyErr_SetExcFromWindowsErrWithUnicodeFilename(
int ierr,
const Py_UNICODE *filename)
{
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
NULL;
PyObject *ret = PyErr_SetExcFromWindowsErrWithFilenameObject(exc,
ierr,
PyObject *ret = PyErr_SetExcFromWindowsErrWithFilenameObject(exc,
ierr,
name);
Py_XDECREF(name);
return ret;
......@@ -466,8 +469,8 @@ PyObject *PyErr_SetFromWindowsErrWithUnicodeFilename(
int ierr,
const Py_UNICODE *filename)
{
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
PyObject *name = filename ?
PyUnicode_FromUnicode(filename, wcslen(filename)) :
NULL;
PyObject *result = PyErr_SetExcFromWindowsErrWithFilenameObject(
PyExc_WindowsError,
......@@ -574,7 +577,24 @@ PyErr_WriteUnraisable(PyObject *obj)
if (f != NULL) {
PyFile_WriteString("Exception ", f);
if (t) {
PyFile_WriteObject(t, f, Py_PRINT_RAW);
char* className = PyExceptionClass_Name(t);
PyObject* moduleName =
PyObject_GetAttrString(t, "__module__");
if (moduleName == NULL)
PyFile_WriteString("<unknown>", f);
else {
char* modstr = PyString_AsString(moduleName);
if (modstr)
{
PyFile_WriteString(modstr, f);
PyFile_WriteString(".", f);
}
}
if (className == NULL)
PyFile_WriteString("<unknown>", f);
else
PyFile_WriteString(className, f);
if (v && v != Py_None) {
PyFile_WriteString(": ", f);
PyFile_WriteObject(v, f, 0);
......@@ -726,7 +746,7 @@ PyErr_SyntaxLocation(const char *filename, int lineno)
/* com_fetch_program_text will attempt to load the line of text that
the exception refers to. If it fails, it will return NULL but will
not set an exception.
not set an exception.
XXX The functionality of this function is quite similar to the
functionality in tb_displayline() in traceback.c.
......
This diff is collapsed.
......@@ -976,7 +976,7 @@ handle_system_exit(void)
fflush(stdout);
if (value == NULL || value == Py_None)
goto done;
if (PyInstance_Check(value)) {
if (PyExceptionInstance_Check(value)) {
/* The error code should be in the `code' attribute. */
PyObject *code = PyObject_GetAttrString(value, "code");
if (code) {
......@@ -1106,11 +1106,10 @@ void PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
if (err) {
/* Don't do anything else */
}
else if (PyClass_Check(exception)) {
PyClassObject* exc = (PyClassObject*)exception;
PyObject* className = exc->cl_name;
else if (PyExceptionClass_Check(exception)) {
char* className = PyExceptionClass_Name(exception);
PyObject* moduleName =
PyDict_GetItemString(exc->cl_dict, "__module__");
PyObject_GetAttrString(exception, "__module__");
if (moduleName == NULL)
err = PyFile_WriteString("<unknown>", f);
......@@ -1126,8 +1125,7 @@ void PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
if (className == NULL)
err = PyFile_WriteString("<unknown>", f);
else
err = PyFile_WriteObject(className, f,
Py_PRINT_RAW);
err = PyFile_WriteString(className, f);
}
}
else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment