diff options
-rw-r--r-- | Lib/decimal.py | 75 | ||||
-rw-r--r-- | Lib/test/test_decimal.py | 30 |
2 files changed, 55 insertions, 50 deletions
diff --git a/Lib/decimal.py b/Lib/decimal.py index 5565052..973f110 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -9,13 +9,8 @@ # Todo: -# Add deepcopy and pickle support for contexts -# Consider having a SimpleDecimal subclass implementing X3.274 semantics -# Improve the Context API -# Especially with respect to setting flags and traps -# Consider adding a clear_flags() method to Context # Provide a clean way of attaching monetary format representations -# Review all exposed constants for utility vs. namespace clutter +# Make tests independent of DefaultContext.prec == 9 """ @@ -139,8 +134,7 @@ __all__ = [ # Constants for use in setting up contexts 'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING', 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', - 'NEVER_ROUND', 'ALWAYS_ROUND', - 'ExceptionList', # <-- Used for building trap/flag dictionaries + 'Signals', # <-- Used for building trap/flag dictionaries # Functions for manipulating contexts 'setcontext', 'getcontext', @@ -244,6 +238,12 @@ class InvalidOperation(DecimalException): return Decimal( (args[1]._sign, args[1]._int, 'n') ) return NaN +# XXX Is there a logic error in subclassing InvalidOperation? +# Setting the InvalidOperation trap to zero does not preclude ConversionSyntax. +# Also, incrementing Conversion syntax flag will not increment InvalidOperation. +# Both of these issues interfere with cross-language portability because +# code following the spec would not know about the Python subclasses. + class ConversionSyntax(InvalidOperation): """Trying to convert badly formed string. @@ -410,8 +410,8 @@ def _filterfunc(obj): except TypeError: return False -#ExceptionList holds the exceptions -ExceptionList = filter(_filterfunc, globals().values()) +#Signals holds the exceptions +Signals = filter(_filterfunc, globals().values()) del _filterfunc @@ -492,7 +492,10 @@ class Decimal(object): self._sign = sign self._int = tuple(map(int, diag)) #Diagnostic info return - self._convertString(value, context) + try: + self._sign, self._int, self._exp = _string2exact(value) + except ValueError: + self._sign, self._int, self._exp = context._raise_error(ConversionSyntax) return # tuple/list conversion (possibly from as_tuple()) @@ -594,19 +597,6 @@ class Decimal(object): return other return 0 - def _convertString(self, value, context=None): - """Changes self's value to that in a string. - - A bad string causes a ConversionSyntax error. - """ - if context is None: - context = getcontext() - try: - self._sign, self._int, self._exp = _string2exact(value) - except ValueError: - self._sign, self._int, self._exp = context._raise_error(ConversionSyntax) - return - def __nonzero__(self): """Is the number non-zero? @@ -1433,8 +1423,6 @@ class Decimal(object): def __int__(self): """Converts self to a int, truncating if necessary.""" - # XXX This should be implemented in terms of tested - # functions in the standard if self._isnan(): context = getcontext() return context._raise_error(InvalidContext) @@ -2115,6 +2103,8 @@ class Decimal(object): return self # My components are also immutable return self.__class__(str(self)) +##### Context class ########################################### + # get rounding method function: rounding_functions = [name for name in Decimal.__dict__.keys() if name.startswith('_round_')] @@ -2152,6 +2142,8 @@ class Context(object): Emin=DEFAULT_MIN_EXPONENT, Emax=DEFAULT_MAX_EXPONENT, capitals=1, _clamp=0, _ignored_flags=[]): + if flags is None: + flags = dict.fromkeys(Signals, 0) DefaultLock.acquire() for name, val in locals().items(): if val is None: @@ -2161,13 +2153,17 @@ class Context(object): DefaultLock.release() del self.self + def clear_flags(self): + """Reset all flags to zero""" + for flag in self.flags: + self.flag = 0 + def copy(self): """Returns a copy from self.""" nc = Context(self.prec, self.rounding, self.trap_enablers, self.flags, self._rounding_decision, self.Emin, self.Emax, self.capitals, self._clamp, self._ignored_flags) return nc - __copy__ = copy def _raise_error(self, error, explanation = None, *args): """Handles an error @@ -2192,7 +2188,7 @@ class Context(object): def _ignore_all_flags(self): """Ignore all flags, if they are raised""" - return self._ignore_flags(*ExceptionList) + return self._ignore_flags(*Signals) def _ignore_flags(self, *flags): """Ignore the flags, if they are raised""" @@ -2959,20 +2955,16 @@ def isnan(num): ##### Setup Specific Contexts ################################ -def _zero_exceptions(): - "Helper function mapping all exceptions to zero." - d = {} - for exception in ExceptionList: - d[exception] = 0 - return d +_basic_traps = dict.fromkeys(Signals, 1) +_basic_traps.update({Inexact:0, Rounded:0, Subnormal:0}) # The default context prototype used by Context() # Is mutable, so than new contexts can have different default values DefaultContext = Context( prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN, - trap_enablers=_zero_exceptions(), - flags=_zero_exceptions(), + trap_enablers=dict.fromkeys(Signals, 0), + flags=None, _rounding_decision=ALWAYS_ROUND, ) @@ -2981,25 +2973,22 @@ DefaultContext = Context( # contexts and be able to reproduce results from other implementations # of the spec. -_basic_traps = _zero_exceptions() -_basic_traps.update({Inexact:1, Rounded:1, Subnormal:1}) - BasicDefaultContext = Context( prec=9, rounding=ROUND_HALF_UP, trap_enablers=_basic_traps, - flags=_zero_exceptions(), + flags=None, _rounding_decision=ALWAYS_ROUND, ) ExtendedDefaultContext = Context( prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN, - trap_enablers=_zero_exceptions(), - flags=_zero_exceptions(), + trap_enablers=dict.fromkeys(Signals, 0), + flags=None, _rounding_decision=ALWAYS_ROUND, ) -##### Useful Constants (internal use only###################### +##### Useful Constants (internal use only) #################### #Reusable defaults Inf = Decimal('Inf') diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index b3fb0ad..6da658f 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -35,7 +35,12 @@ from test.test_support import TestSkipped, run_unittest, run_doctest, is_resourc import threading TESTDATADIR = 'decimaltestdata' -dir = os.curdir + os.sep + TESTDATADIR + os.sep +if __name__ == '__main__': + file = sys.argv[0] +else: + file = __file__ +testdir = os.path.dirname(file) or os.curdir +dir = testdir + os.sep + TESTDATADIR + os.sep skip_expected = not os.path.isdir(dir) @@ -190,7 +195,7 @@ class DecimalTest(unittest.TestCase): quote = 0 theirexceptions = [ErrorNames[x.lower()] for x in exceptions] - for exception in ExceptionList: + for exception in Signals: self.context.trap_enablers[exception] = 1 #Catch these bugs... for exception in theirexceptions: self.context.trap_enablers[exception] = 0 @@ -212,7 +217,7 @@ class DecimalTest(unittest.TestCase): funct(self.context.create_decimal(v)) except error: pass - except ExceptionList, e: + except Signals, e: self.fail("Raised %s in %s when %s disabled" % \ (e, s, error)) else: @@ -232,7 +237,7 @@ class DecimalTest(unittest.TestCase): funct(*vals) except error: pass - except ExceptionList, e: + except Signals, e: self.fail("Raised %s in %s when %s disabled" % \ (e, s, error)) else: @@ -242,7 +247,7 @@ class DecimalTest(unittest.TestCase): result = str(funct(*vals)) if fname == 'same_quantum': result = str(int(eval(result))) # 'True', 'False' -> '1', '0' - except ExceptionList, error: + except Signals, error: self.fail("Raised %s in %s" % (error, s)) except: #Catch any error long enough to state the test case. print "ERROR:", s @@ -263,13 +268,13 @@ class DecimalTest(unittest.TestCase): def getexceptions(self): L = [] - for exception in ExceptionList: + for exception in Signals: if self.context.flags[exception]: L.append(exception) return L def resetflags(self): - for exception in ExceptionList: + for exception in Signals: self.context.flags[exception] = 0 def change_precision(self, prec): @@ -1046,6 +1051,16 @@ class DecimalPythonAPItests(unittest.TestCase): e = pickle.loads(p) self.assertEqual(d, e) +class ContextAPItests(unittest.TestCase): + + def test_pickle(self): + c = Context() + e = pickle.loads(pickle.dumps(c)) + for k in vars(c): + v1 = vars(c)[k] + v2 = vars(e)[k] + self.assertEqual(v1, v2) + def test_main(arith=False, verbose=None): """ Execute the tests. @@ -1059,6 +1074,7 @@ def test_main(arith=False, verbose=None): DecimalUseOfContextTest, DecimalUsabilityTest, DecimalPythonAPItests, + ContextAPItests, ] if arith or is_resource_enabled('decimal'): |