From 5aa478badfabb005e6f17c98fcf007a0bc54eecc Mon Sep 17 00:00:00 2001
From: Raymond Hettinger <python@rcn.com>
Date: Fri, 9 Jul 2004 10:02:53 +0000
Subject: Module and tests: * Map conditions to related signals. * Make
 contexts unhashable. * Eliminate used "default" attribute in exception
 definitions. * Eliminate the _filterfunc in favor of a straight list.

Docs:
* Eliminate documented references to conditions that are not signals.
* Eliminate parenthetical notes such as "1/0 --> Inf" which are no
  longer true with the new defaults.
---
 Doc/lib/libdecimal.tex   | 54 ++++++----------------------------------
 Lib/decimal.py           | 65 ++++++++++++++++++++++--------------------------
 Lib/test/test_decimal.py | 11 ++++----
 Misc/NEWS                |  3 +++
 4 files changed, 45 insertions(+), 88 deletions(-)

diff --git a/Doc/lib/libdecimal.tex b/Doc/lib/libdecimal.tex
index 3b41d6a..b6164fc 100644
--- a/Doc/lib/libdecimal.tex
+++ b/Doc/lib/libdecimal.tex
@@ -66,10 +66,8 @@ context for arithmetic, and signals.
 A decimal number is immutable.  It has a sign, coefficient digits, and an
 exponent.  To preserve significance, the coefficient digits do not truncate
 trailing zeroes.  Decimals also include special values such as
-\constant{Infinity} (the result of \samp{1 / 0}), \constant{-Infinity},
-(the result of \samp{-1 / 0}), and \constant{NaN} (the result of
-\samp{0 / 0}).  The standard also differentiates \constant{-0} from
-\constant{+0}.
+\constant{Infinity}, \constant{-Infinity}, and \constant{NaN}.  The standard
+also differentiates \constant{-0} from \constant{+0}.
                                                    
 The context for arithmetic is an environment specifying precision, rounding
 rules, limits on exponents, flags that indicate the results of operations,
@@ -82,9 +80,7 @@ Signals are types of information that arise during the course of a
 computation.  Depending on the needs of the application, some signals may be
 ignored, considered as informational, or treated as exceptions. The signals in
 the decimal module are: \constant{Clamped}, \constant{InvalidOperation},
-\constant{ConversionSyntax}, \constant{DivisionByZero},
-\constant{DivisionImpossible}, \constant{DivisionUndefined},
-\constant{Inexact}, \constant{InvalidContext}, \constant{Rounded},
+\constant{DivisionByZero}, \constant{Inexact}, \constant{Rounded},
 \constant{Subnormal}, \constant{Overflow}, and \constant{Underflow}.
 
 For each signal there is a flag and a trap enabler.  When a signal is
@@ -124,7 +120,7 @@ Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
 Decimal instances can be constructed from integers, strings or tuples.  To
 create a Decimal from a \class{float}, first convert it to a string.  This
 serves as an explicit reminder of the details of the conversion (including
-representation error).  Malformed strings signal \constant{ConversionSyntax}
+representation error).  Malformed strings signal \constant{InvalidOperation}
 and return a special kind of Decimal called a \constant{NaN} which stands for
 ``Not a number''. Positive and negative \constant{Infinity} is yet another
 special kind of Decimal.        
@@ -274,10 +270,8 @@ To turn all the traps on or off all at once, use a loop.  Also, the
 >>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0})
 >>> getcontext()
 Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
-        setflags=[], settraps=['Underflow', 'DecimalException', 'Clamped',
-        'InvalidContext', 'InvalidOperation', 'ConversionSyntax',
-        'DivisionByZero', 'DivisionImpossible', 'DivisionUndefined',
-        'Overflow'])
+        setflags=[], settraps=['Clamped', 'Underflow', 'InvalidOperation',
+        'DivisionByZero', 'Overflow'])
 \end{verbatim}
 
 Applications typically set the context once at the beginning of a program
@@ -320,7 +314,7 @@ as other Python numeric types.
 
   The supplied \var{context} or, if not specified, the current context
   governs only the handling of malformed strings not conforming to the
-  numeric string syntax.  If the context traps \constant{ConversionSyntax},
+  numeric string syntax.  If the context traps \constant{InvalidOperation},
   an exception is raised; otherwise, the constructor returns a new Decimal
   with the value of \constant{NaN}.
 
@@ -730,13 +724,6 @@ exception is raised upon encountering the condition.
     reduced to fit by adding zeroes to the coefficient.
 \end{classdesc*}
 
-\begin{classdesc*}{ConversionSyntax}
-    Trying to convert a malformed string such as:  \code{Decimal('jump')}.
-
-    Decimal converts only strings conforming to the numeric string
-    syntax.  If this signal is not trapped, returns \constant{NaN}.
-\end{classdesc*}
-
 \begin{classdesc*}{DecimalException}
     Base class for other signals.
 \end{classdesc*}
@@ -750,19 +737,6 @@ exception is raised upon encountering the condition.
     the inputs to the calculation.
 \end{classdesc*}
 
-\begin{classdesc*}{DivisionImpossible}
-    Error performing a division operation.  Caused when an intermediate result
-    has more digits that the allowed by the current precision.  If not trapped,
-    returns \constant{NaN}.
-\end{classdesc*}
-
-
-\begin{classdesc*}{DivisionUndefined}
-    This is a subclass of \class{DivisionByZero}.
-
-    It occurs only in the context of division operations.
-\end{classdesc*}
-
 \begin{classdesc*}{Inexact}
     Indicates that rounding occurred and the result is not exact.
 
@@ -771,14 +745,6 @@ exception is raised upon encountering the condition.
     to detect when results are inexact.
 \end{classdesc*}
 
-
-\begin{classdesc*}{InvalidContext}
-    This is a subclass of \class{InvalidOperation}.
-
-    Indicates an error within the Context object such as an unknown
-    rounding operation.  If not trapped, returns \constant{NaN}.
-\end{classdesc*}
-
 \begin{classdesc*}{InvalidOperation}
     An invalid operation was performed.
 
@@ -809,7 +775,6 @@ exception is raised upon encountering the condition.
     \class{Rounded} are also signaled.   
 \end{classdesc*}
 
-
 \begin{classdesc*}{Rounded}
     Rounding occurred though possibly no information was lost.
 
@@ -844,16 +809,11 @@ The following table summarizes the hierarchy of signals:
                 Overflow(Inexact, Rounded)
                 Underflow(Inexact, Rounded, Subnormal)
             InvalidOperation
-                ConversionSyntax
-                DivisionImpossible
-                DivisionUndefined(InvalidOperation, exceptions.ZeroDivisionError)
-                InvalidContext
             Rounded
             Subnormal
 \end{verbatim}            
 
 
-
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 \subsection{Working with threads \label{decimal-threads}}
 
diff --git a/Lib/decimal.py b/Lib/decimal.py
index 4e5cbbb..9f77b24 100644
--- a/Lib/decimal.py
+++ b/Lib/decimal.py
@@ -78,30 +78,30 @@ Traceback (most recent call last):
   ...
 DivisionByZero: x / 0
 >>> c = Context()
->>> c.trap_enablers[DivisionUndefined] = 0
->>> print c.flags[DivisionUndefined]
+>>> c.trap_enablers[InvalidOperation] = 0
+>>> print c.flags[InvalidOperation]
 0
 >>> c.divide(Decimal(0), Decimal(0))
 Decimal("NaN")
->>> c.trap_enablers[DivisionUndefined] = 1
->>> print c.flags[DivisionUndefined]
+>>> c.trap_enablers[InvalidOperation] = 1
+>>> print c.flags[InvalidOperation]
 1
->>> c.flags[DivisionUndefined] = 0
->>> print c.flags[DivisionUndefined]
+>>> c.flags[InvalidOperation] = 0
+>>> print c.flags[InvalidOperation]
 0
 >>> print c.divide(Decimal(0), Decimal(0))
 Traceback (most recent call last):
   ...
   ...
   ...
-DivisionUndefined: 0 / 0
->>> print c.flags[DivisionUndefined]
+InvalidOperation: 0 / 0
+>>> print c.flags[InvalidOperation]
 1
->>> c.flags[DivisionUndefined] = 0
->>> c.trap_enablers[DivisionUndefined] = False
+>>> c.flags[InvalidOperation] = 0
+>>> c.trap_enablers[InvalidOperation] = 0
 >>> print c.divide(Decimal(0), Decimal(0))
 NaN
->>> print c.flags[DivisionUndefined]
+>>> print c.flags[InvalidOperation]
 1
 >>>
 """
@@ -152,7 +152,7 @@ ALWAYS_ROUND = 'ALWAYS_ROUND'  # Every operation rounds at end.
 #Errors
 
 class DecimalException(ArithmeticError):
-    """Base exception class, defines default things.
+    """Base exception class.
 
     Used exceptions derive from this.
     If an exception derives from another exception besides this (such as
@@ -160,12 +160,6 @@ class DecimalException(ArithmeticError):
     called if the others are present.  This isn't actually used for
     anything, though.
 
-    Attributes:
-
-    default -- If the context is basic, the trap_enablers are set to
-               this by default.  Extended contexts start out with them set
-               to 0, regardless.
-
     handle  -- Called when context._raise_error is called and the
                trap_enabler is set.  First argument is self, second is the
                context.  More arguments can be given, those being after
@@ -176,7 +170,6 @@ class DecimalException(ArithmeticError):
     To define a new exception, it should be sufficient to have it derive
     from DecimalException.
     """
-    default = 1
     def handle(self, context, *args):
         pass
 
@@ -288,7 +281,7 @@ class Inexact(DecimalException):
     The inexact signal may be tested (or trapped) to determine if a given
     operation (or sequence of operations) was inexact.
     """
-    default = 0
+    pass
 
 class InvalidContext(InvalidOperation):
     """Invalid context.  Unknown rounding, for example.
@@ -315,7 +308,7 @@ class Rounded(DecimalException):
     The rounded signal may be tested (or trapped) to determine if a given
     operation (or sequence of operations) caused a loss of precision.
     """
-    default = 0
+    pass
 
 class Subnormal(DecimalException):
     """Exponent < Emin before rounding.
@@ -382,19 +375,15 @@ class Underflow(Inexact, Rounded, Subnormal):
     In all cases, Inexact, Rounded, and Subnormal will also be raised.
     """
 
+# List of public traps and flags
+Signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
+           Underflow, InvalidOperation, Subnormal]
 
-def _filterfunc(obj):
-    """Returns true if a subclass of DecimalException"""
-    try:
-        return issubclass(obj, DecimalException)
-    except TypeError:
-        return False
-
-#Signals holds the exceptions
-Signals = filter(_filterfunc, globals().values())
-
-del _filterfunc
-
+# Map conditions (per the spec) to signals
+_condition_map = {ConversionSyntax:InvalidOperation,
+                  DivisionImpossible:InvalidOperation,
+                  DivisionUndefined:InvalidOperation,
+                  InvalidContext:InvalidOperation}
 
 ##### Context Functions #######################################
 
@@ -2168,7 +2157,7 @@ class Context(object):
         return nc
     __copy__ = copy
 
-    def _raise_error(self, error, explanation = None, *args):
+    def _raise_error(self, condition, explanation = None, *args):
         """Handles an error
 
         If the flag is in _ignored_flags, returns the default response.
@@ -2176,6 +2165,7 @@ class Context(object):
         trap_enabler is set, it reaises the exception.  Otherwise, it returns
         the default value after incrementing the flag.
         """
+        error = _condition_map.get(condition, condition)
         if error in self._ignored_flags:
             #Don't touch the flag
             return error().handle(self, *args)
@@ -2183,7 +2173,7 @@ class Context(object):
         self.flags[error] += 1
         if not self.trap_enablers[error]:
             #The errors define how to handle themselves.
-            return error().handle(self, *args)
+            return condition().handle(self, *args)
 
         # Errors should only be risked on copies of the context
         #self._ignored_flags = []
@@ -2207,6 +2197,11 @@ class Context(object):
         for flag in flags:
             self._ignored_flags.remove(flag)
 
+    def __hash__(self):
+        """A Context cannot be hashed."""
+        # We inherit object.__hash__, so we must deny this explicitly
+        raise TypeError, "Cannot hash a Context."
+
     def Etiny(self):
         """Returns Etiny (= Emin - prec + 1)"""
         return int(self.Emin - self.prec + 1)
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index a03b784..e6d9389 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -60,12 +60,12 @@ EXTENDEDERRORTEST = False
 #Map the test cases' error names to the actual errors
 
 ErrorNames = {'clamped' : Clamped,
-              'conversion_syntax' : ConversionSyntax,
+              'conversion_syntax' : InvalidOperation,
               'division_by_zero' : DivisionByZero,
-              'division_impossible' : DivisionImpossible,
-              'division_undefined' : DivisionUndefined,
+              'division_impossible' : InvalidOperation,
+              'division_undefined' : InvalidOperation,
               'inexact' : Inexact,
-              'invalid_context' : InvalidContext,
+              'invalid_context' : InvalidOperation,
               'invalid_operation' : InvalidOperation,
               'overflow' : Overflow,
               'rounded' : Rounded,
@@ -131,6 +131,7 @@ class DecimalTest(unittest.TestCase):
             return
         for line in open(file).xreadlines():
             line = line.replace('\r\n', '').replace('\n', '')
+            #print line
             try:
                 t = self.eval_line(line)
             except ConversionSyntax:
@@ -648,7 +649,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
         self.assertEqual(d1, Decimal('-0.625'))
 
     def test_floor_division(self):
-        '''Test floor division in all its ways.'''
 
         d1 = Decimal('5')
         d2 = Decimal('2')
@@ -676,7 +676,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
         self.assertEqual(d1, Decimal('1'))
 
     def test_powering(self):
-        '''Test powering in all its ways.'''
 
         d1 = Decimal('5')
         d2 = Decimal('2')
diff --git a/Misc/NEWS b/Misc/NEWS
index df3dcca..b7a1ade 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -26,6 +26,9 @@ Extension modules
 Library
 -------
 
+- decimal.py now only uses signals in the spec.  The other conditions are
+  no longer part of the public API.
+
 Tools/Demos
 -----------
 
-- 
cgit v0.12

