Skip to content

Commit bf36409

Browse files
committed
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.
1 parent 7624674 commit bf36409

File tree

16 files changed

+570
-232
lines changed

16 files changed

+570
-232
lines changed

Include/pyerrors.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,32 @@ PyAPI_FUNC(int) PyErr_GivenExceptionMatches(PyObject *, PyObject *);
2626
PyAPI_FUNC(int) PyErr_ExceptionMatches(PyObject *);
2727
PyAPI_FUNC(void) PyErr_NormalizeException(PyObject**, PyObject**, PyObject**);
2828

29+
/* */
2930

31+
#define PyExceptionClass_Check(x) \
32+
(PyClass_Check((x)) \
33+
|| (PyType_Check((x)) && PyType_IsSubtype( \
34+
(PyTypeObject*)(x), (PyTypeObject*)PyExc_BaseException)))
35+
36+
37+
#define PyExceptionInstance_Check(x) \
38+
(PyInstance_Check((x)) || \
39+
(PyType_IsSubtype((x)->ob_type, (PyTypeObject*)PyExc_BaseException)))
40+
41+
#define PyExceptionClass_Name(x) \
42+
(PyClass_Check((x)) \
43+
? PyString_AS_STRING(((PyClassObject*)(x))->cl_name) \
44+
: (char *)(((PyTypeObject*)(x))->tp_name))
45+
46+
#define PyExceptionInstance_Class(x) \
47+
((PyInstance_Check((x)) \
48+
? (PyObject*)((PyInstanceObject*)(x))->in_class \
49+
: (PyObject*)((x)->ob_type)))
50+
51+
3052
/* Predefined exceptions */
3153

54+
PyAPI_DATA(PyObject *) PyExc_BaseException;
3255
PyAPI_DATA(PyObject *) PyExc_Exception;
3356
PyAPI_DATA(PyObject *) PyExc_StopIteration;
3457
PyAPI_DATA(PyObject *) PyExc_GeneratorExit;

Lib/test/exception_hierarchy.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
BaseException
2+
+-- SystemExit
3+
+-- KeyboardInterrupt
4+
+-- Exception
5+
+-- GeneratorExit
6+
+-- StopIteration
7+
+-- StandardError
8+
| +-- ArithmeticError
9+
| | +-- FloatingPointError
10+
| | +-- OverflowError
11+
| | +-- ZeroDivisionError
12+
| +-- AssertionError
13+
| +-- AttributeError
14+
| +-- EnvironmentError
15+
| | +-- IOError
16+
| | +-- OSError
17+
| | +-- WindowsError (Windows)
18+
| +-- EOFError
19+
| +-- ImportError
20+
| +-- LookupError
21+
| | +-- IndexError
22+
| | +-- KeyError
23+
| +-- MemoryError
24+
| +-- NameError
25+
| | +-- UnboundLocalError
26+
| +-- ReferenceError
27+
| +-- RuntimeError
28+
| | +-- NotImplementedError
29+
| +-- SyntaxError
30+
| | +-- IndentationError
31+
| | +-- TabError
32+
| +-- SystemError
33+
| +-- TypeError
34+
| +-- ValueError
35+
| | +-- UnicodeError
36+
| | +-- UnicodeDecodeError
37+
| | +-- UnicodeEncodeError
38+
| | +-- UnicodeTranslateError
39+
+-- Warning
40+
+-- DeprecationWarning
41+
+-- PendingDeprecationWarning
42+
+-- RuntimeWarning
43+
+-- SyntaxWarning
44+
+-- UserWarning
45+
+-- FutureWarning
46+
+-- OverflowWarning [not generated by the interpreter]

Lib/test/output/test_logging

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -488,12 +488,12 @@ INFO:a.b.c.d:Info 5
488488
-- log_test4 begin ---------------------------------------------------
489489
config0: ok.
490490
config1: ok.
491-
config2: exceptions.AttributeError
492-
config3: exceptions.KeyError
491+
config2: <class 'exceptions.AttributeError'>
492+
config3: <class 'exceptions.KeyError'>
493493
-- log_test4 end ---------------------------------------------------
494494
-- log_test5 begin ---------------------------------------------------
495495
ERROR:root:just testing
496-
exceptions.KeyError... Don't panic!
496+
<class 'exceptions.KeyError'>... Don't panic!
497497
-- log_test5 end ---------------------------------------------------
498498
-- logrecv output begin ---------------------------------------------------
499499
ERR -> CRITICAL: Message 0 (via logrecv.tcp.ERR)

Lib/test/test_coercion.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def do_infix_binops():
9696
x = eval('a %s b' % op)
9797
except:
9898
error = sys.exc_info()[:2]
99-
print '... %s' % error[0]
99+
print '... %s.%s' % (error[0].__module__, error[0].__name__)
100100
else:
101101
print '=', format_result(x)
102102
try:
@@ -108,7 +108,7 @@ def do_infix_binops():
108108
exec('z %s= b' % op)
109109
except:
110110
error = sys.exc_info()[:2]
111-
print '... %s' % error[0]
111+
print '... %s.%s' % (error[0].__module__, error[0].__name__)
112112
else:
113113
print '=>', format_result(z)
114114

@@ -121,7 +121,7 @@ def do_prefix_binops():
121121
x = eval('%s(a, b)' % op)
122122
except:
123123
error = sys.exc_info()[:2]
124-
print '... %s' % error[0]
124+
print '... %s.%s' % (error[0].__module__, error[0].__name__)
125125
else:
126126
print '=', format_result(x)
127127

Lib/test/test_descr.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3355,31 +3355,6 @@ class NewClass(object):
33553355
vereq(NewClass.__doc__, 'object=None; type=NewClass')
33563356
vereq(NewClass().__doc__, 'object=NewClass instance; type=NewClass')
33573357

3358-
def string_exceptions():
3359-
if verbose:
3360-
print "Testing string exceptions ..."
3361-
3362-
# Ensure builtin strings work OK as exceptions.
3363-
astring = "An exception string."
3364-
try:
3365-
raise astring
3366-
except astring:
3367-
pass
3368-
else:
3369-
raise TestFailed, "builtin string not usable as exception"
3370-
3371-
# Ensure string subclass instances do not.
3372-
class MyStr(str):
3373-
pass
3374-
3375-
newstring = MyStr("oops -- shouldn't work")
3376-
try:
3377-
raise newstring
3378-
except TypeError:
3379-
pass
3380-
except:
3381-
raise TestFailed, "string subclass allowed as exception"
3382-
33833358
def copy_setstate():
33843359
if verbose:
33853360
print "Testing that copy.*copy() correctly uses __setstate__..."
@@ -4172,7 +4147,6 @@ def test_main():
41724147
funnynew()
41734148
imulbug()
41744149
docdescriptor()
4175-
string_exceptions()
41764150
copy_setstate()
41774151
slices()
41784152
subtype_resurrection()

Lib/test/test_exceptions.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@ def test_raise_catch(exc):
2929

3030
def r(thing):
3131
test_raise_catch(thing)
32-
if isinstance(thing, ClassType):
33-
print thing.__name__
34-
else:
35-
print thing
32+
print getattr(thing, '__name__', thing)
3633

3734
r(AttributeError)
3835
import sys

Lib/test/test_pep352.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import unittest
2+
import __builtin__
3+
import exceptions
4+
import warnings
5+
from test.test_support import run_unittest
6+
import os
7+
from platform import system as platform_system
8+
9+
class ExceptionClassTests(unittest.TestCase):
10+
11+
"""Tests for anything relating to exception objects themselves (e.g.,
12+
inheritance hierarchy)"""
13+
14+
def test_builtins_new_style(self):
15+
self.failUnless(issubclass(Exception, object))
16+
17+
def verify_instance_interface(self, ins):
18+
for attr in ("args", "message", "__str__", "__unicode__", "__repr__",
19+
"__getitem__"):
20+
self.failUnless(hasattr(ins, attr), "%s missing %s attribute" %
21+
(ins.__class__.__name__, attr))
22+
23+
def test_inheritance(self):
24+
# Make sure the inheritance hierarchy matches the documentation
25+
exc_set = set(x for x in dir(exceptions) if not x.startswith('_'))
26+
inheritance_tree = open(os.path.join(os.path.split(__file__)[0],
27+
'exception_hierarchy.txt'))
28+
try:
29+
superclass_name = inheritance_tree.readline().rstrip()
30+
try:
31+
last_exc = getattr(__builtin__, superclass_name)
32+
except AttributeError:
33+
self.fail("base class %s not a built-in" % superclass_name)
34+
self.failUnless(superclass_name in exc_set)
35+
exc_set.discard(superclass_name)
36+
superclasses = [] # Loop will insert base exception
37+
last_depth = 0
38+
for exc_line in inheritance_tree:
39+
exc_line = exc_line.rstrip()
40+
depth = exc_line.rindex('-')
41+
exc_name = exc_line[depth+2:] # Slice past space
42+
if '(' in exc_name:
43+
paren_index = exc_name.index('(')
44+
platform_name = exc_name[paren_index+1:-1]
45+
if platform_system() != platform_name:
46+
exc_set.discard(exc_name)
47+
continue
48+
if '[' in exc_name:
49+
left_bracket = exc_name.index('[')
50+
exc_name = exc_name[:left_bracket-1] # cover space
51+
try:
52+
exc = getattr(__builtin__, exc_name)
53+
except AttributeError:
54+
self.fail("%s not a built-in exception" % exc_name)
55+
if last_depth < depth:
56+
superclasses.append((last_depth, last_exc))
57+
elif last_depth > depth:
58+
while superclasses[-1][0] >= depth:
59+
superclasses.pop()
60+
self.failUnless(issubclass(exc, superclasses[-1][1]),
61+
"%s is not a subclass of %s" % (exc.__name__,
62+
superclasses[-1][1].__name__))
63+
try: # Some exceptions require arguments; just skip them
64+
self.verify_instance_interface(exc())
65+
except TypeError:
66+
pass
67+
self.failUnless(exc_name in exc_set)
68+
exc_set.discard(exc_name)
69+
last_exc = exc
70+
last_depth = depth
71+
finally:
72+
inheritance_tree.close()
73+
self.failUnlessEqual(len(exc_set), 0, "%s not accounted for" % exc_set)
74+
75+
interface_tests = ("length", "args", "message", "str", "unicode", "repr",
76+
"indexing")
77+
78+
def interface_test_driver(self, results):
79+
for test_name, (given, expected) in zip(self.interface_tests, results):
80+
self.failUnlessEqual(given, expected, "%s: %s != %s" % (test_name,
81+
given, expected))
82+
83+
def test_interface_single_arg(self):
84+
# Make sure interface works properly when given a single argument
85+
arg = "spam"
86+
exc = Exception(arg)
87+
results = ([len(exc.args), 1], [exc.args[0], arg], [exc.message, arg],
88+
[str(exc), str(arg)], [unicode(exc), unicode(arg)],
89+
[repr(exc), exc.__class__.__name__ + repr(exc.args)], [exc[0], arg])
90+
self.interface_test_driver(results)
91+
92+
def test_interface_multi_arg(self):
93+
# Make sure interface correct when multiple arguments given
94+
arg_count = 3
95+
args = tuple(range(arg_count))
96+
exc = Exception(*args)
97+
results = ([len(exc.args), arg_count], [exc.args, args],
98+
[exc.message, ''], [str(exc), str(args)],
99+
[unicode(exc), unicode(args)],
100+
[repr(exc), exc.__class__.__name__ + repr(exc.args)],
101+
[exc[-1], args[-1]])
102+
self.interface_test_driver(results)
103+
104+
def test_interface_no_arg(self):
105+
# Make sure that with no args that interface is correct
106+
exc = Exception()
107+
results = ([len(exc.args), 0], [exc.args, tuple()], [exc.message, ''],
108+
[str(exc), ''], [unicode(exc), u''],
109+
[repr(exc), exc.__class__.__name__ + '()'], [True, True])
110+
self.interface_test_driver(results)
111+
112+
class UsageTests(unittest.TestCase):
113+
114+
"""Test usage of exceptions"""
115+
116+
def setUp(self):
117+
self._filters = warnings.filters[:]
118+
119+
def tearDown(self):
120+
warnings.filters = self._filters[:]
121+
122+
def test_raise_classic(self):
123+
class ClassicClass:
124+
pass
125+
try:
126+
raise ClassicClass
127+
except ClassicClass:
128+
pass
129+
except:
130+
self.fail("unable to raise classic class")
131+
try:
132+
raise ClassicClass()
133+
except ClassicClass:
134+
pass
135+
except:
136+
self.fail("unable to raise class class instance")
137+
138+
def test_raise_new_style_non_exception(self):
139+
class NewStyleClass(object):
140+
pass
141+
try:
142+
raise NewStyleClass
143+
except TypeError:
144+
pass
145+
except:
146+
self.fail("unable to raise new-style class")
147+
try:
148+
raise NewStyleClass()
149+
except TypeError:
150+
pass
151+
except:
152+
self.fail("unable to raise new-style class instance")
153+
154+
def test_raise_string(self):
155+
warnings.resetwarnings()
156+
warnings.filterwarnings("error")
157+
try:
158+
raise "spam"
159+
except DeprecationWarning:
160+
pass
161+
except:
162+
self.fail("raising a string did not cause a DeprecationWarning")
163+
164+
def test_catch_string(self):
165+
# Test will be pertinent when catching exceptions raises a
166+
# DeprecationWarning
167+
warnings.filterwarnings("ignore", "raising")
168+
str_exc = "spam"
169+
try:
170+
raise str_exc
171+
except str_exc:
172+
pass
173+
except:
174+
self.fail("catching a string exception failed")
175+
176+
def test_main():
177+
run_unittest(ExceptionClassTests, UsageTests)
178+
179+
180+
181+
if __name__ == '__main__':
182+
test_main()

Lib/traceback.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ def format_exception_only(etype, value):
157157
which exception occurred is the always last string in the list.
158158
"""
159159
list = []
160-
if type(etype) == types.ClassType:
160+
if (type(etype) == types.ClassType
161+
or (isinstance(etype, type) and issubclass(etype, Exception))):
161162
stype = etype.__name__
162163
else:
163164
stype = etype

Lib/warnings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
145145
assert action in ("error", "ignore", "always", "default", "module",
146146
"once"), "invalid action: %r" % (action,)
147147
assert isinstance(message, basestring), "message must be a string"
148-
assert isinstance(category, types.ClassType), "category must be a class"
148+
assert isinstance(category, (type, types.ClassType)), \
149+
"category must be a class"
149150
assert issubclass(category, Warning), "category must be a Warning subclass"
150151
assert isinstance(module, basestring), "module must be a string"
151152
assert isinstance(lineno, int) and lineno >= 0, \

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 2.5 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- PEP 352, patch #1104669: Make exceptions new-style objects. Introduced the
16+
new exception base class, BaseException, which has a new message attribute.
17+
KeyboardInterrupt and SystemExit to directly inherit from BaseException now.
18+
Raising a string exception now raises a DeprecationWarning.
19+
1520
- Patch #1438387, PEP 328: relative and absolute imports. Imports can now be
1621
explicitly relative, using 'from .module import name' to mean 'from the same
1722
package as this module is in. Imports without dots still default to the

0 commit comments

Comments
 (0)