From bf4406971ce34434ed914d17fab70370948d2aab Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 10 Jul 2004 14:14:37 +0000 Subject: Improve Context construction and representation: * Rename "trap_enablers" to just "traps". * Simplify names of "settraps" and "setflags" to just "traps" and "flags". * Show "capitals" in the context representation * Simplify the Context constructor to match its repr form so that only the set flags and traps need to be listed. * Representation can now be run through eval(). Improve the error message when the Decimal constructor is given a float. The test suite no longer needs a duplicate reset_flags method. --- Doc/lib/libdecimal.tex | 30 +++++++++++++-------------- Lib/decimal.py | 54 ++++++++++++++++++++++++------------------------ Lib/test/test_decimal.py | 28 +++++++++++-------------- 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/Doc/lib/libdecimal.tex b/Doc/lib/libdecimal.tex index b6164fc..a00feb3 100644 --- a/Doc/lib/libdecimal.tex +++ b/Doc/lib/libdecimal.tex @@ -112,7 +112,7 @@ precision, rounding, or trap enablers: >>> from decimal import * >>> getcontext() Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - setflags=[], settraps=[]) + capitals=1, flags=[], traps=[]) >>> getcontext().prec = 7 \end{verbatim} @@ -204,10 +204,10 @@ because many of the traps are enabled: >>> myothercontext = Context(prec=60, rounding=ROUND_HALF_DOWN) >>> myothercontext Context(prec=60, rounding=ROUND_HALF_DOWN, Emin=-999999999, Emax=999999999, - setflags=[], settraps=[]) + capitals=1, flags=[], traps=[]) >>> ExtendedContext Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - setflags=[], settraps=[]) + capitals=1, flags=[], traps=[]) >>> setcontext(myothercontext) >>> Decimal(1) / Decimal(7) Decimal("0.142857142857142857142857142857142857142857142857142857142857") @@ -236,21 +236,21 @@ clear the flags before each set of monitored computations by using the Decimal("3.14159292") >>> getcontext() Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - setflags=['Inexact', 'Rounded'], settraps=[]) + capitals=1, flags=[Inexact, Rounded], traps=[]) \end{verbatim} -The \var{setflags} entry shows that the rational approximation to +The \var{flags} entry shows that the rational approximation to \constant{Pi} was rounded (digits beyond the context precision were thrown away) and that the result is inexact (some of the discarded digits were non-zero). -Individual traps are set using the dictionary in the \member{trap_enablers} +Individual traps are set using the dictionary in the \member{traps} field of a context: \begin{verbatim} >>> Decimal(1) / Decimal(0) Decimal("Infinity") ->>> getcontext().trap_enablers[DivisionByZero] = 1 +>>> getcontext().traps[DivisionByZero] = 1 >>> Decimal(1) / Decimal(0) Traceback (most recent call last): @@ -264,14 +264,14 @@ To turn all the traps on or off all at once, use a loop. Also, the \begin{verbatim} >>> getcontext.clear_flags() ->>> for sig in getcontext().trap_enablers: -... getcontext().trap_enablers[sig] = 1 +>>> for sig in getcontext().traps: +... getcontext().traps[sig] = 1 ->>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0}) +>>> getcontext().traps.update({Rounded:0, Inexact:0, Subnormal:0}) >>> getcontext() Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - setflags=[], settraps=['Clamped', 'Underflow', 'InvalidOperation', - 'DivisionByZero', 'Overflow']) + capitals=1, flags=[], traps=[Clamped, Underflow, + InvalidOperation, DivisionByZero, Overflow]) \end{verbatim} Applications typically set the context once at the beginning of a program @@ -489,7 +489,7 @@ In addition, the module provides three pre-made contexts: In addition to the three supplied contexts, new contexts can be created with the \class{Context} constructor. -\begin{classdesc}{Context}{prec=None, rounding=None, trap_enablers=None, +\begin{classdesc}{Context}{prec=None, rounding=None, traps=None, flags=None, Emin=None, Emax=None, capitals=1} Creates a new context. If a field is not specified or is \constant{None}, the default values are copied from the \constant{DefaultContext}. If the @@ -508,7 +508,7 @@ with the \class{Context} constructor. \constant{ROUND_HALF_UP} (away from zero), or \constant{ROUND_UP} (away from zero). - The \var{trap_enablers} and \var{flags} fields are mappings from signals + The \var{traps} and \var{flags} fields are mappings from signals to either \constant{0} or \constant{1}. The \var{Emin} and \var{Emax} fields are integers specifying the outer @@ -839,7 +839,7 @@ be a race condition with threads calling \function{getcontext()}. For example: # Set applicationwide defaults for all threads about to be launched DefaultContext.prec=12 DefaultContext.rounding=ROUND_DOWN -DefaultContext.trap_enablers=dict.fromkeys(Signals, 0) +DefaultContext.traps=dict.fromkeys(Signals, 0) setcontext(DefaultContext) # Now start all of the threads diff --git a/Lib/decimal.py b/Lib/decimal.py index d9c3f10..85e0f34 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -70,7 +70,7 @@ NaN -Infinity >>> print dig / 0 Infinity ->>> getcontext().trap_enablers[DivisionByZero] = 1 +>>> getcontext().traps[DivisionByZero] = 1 >>> print dig / 0 Traceback (most recent call last): ... @@ -78,12 +78,12 @@ Traceback (most recent call last): ... DivisionByZero: x / 0 >>> c = Context() ->>> c.trap_enablers[InvalidOperation] = 0 +>>> c.traps[InvalidOperation] = 0 >>> print c.flags[InvalidOperation] 0 >>> c.divide(Decimal(0), Decimal(0)) Decimal("NaN") ->>> c.trap_enablers[InvalidOperation] = 1 +>>> c.traps[InvalidOperation] = 1 >>> print c.flags[InvalidOperation] 1 >>> c.flags[InvalidOperation] = 0 @@ -98,7 +98,7 @@ InvalidOperation: 0 / 0 >>> print c.flags[InvalidOperation] 1 >>> c.flags[InvalidOperation] = 0 ->>> c.trap_enablers[InvalidOperation] = 0 +>>> c.traps[InvalidOperation] = 0 >>> print c.divide(Decimal(0), Decimal(0)) NaN >>> print c.flags[InvalidOperation] @@ -495,7 +495,11 @@ class Decimal(object): self._int = value._int return - raise TypeError("Can't convert %r" % value) + if isinstance(value, float): + raise TypeError("Cannot convert float to Decimal. " + + "First convert the float to a string") + + raise TypeError("Cannot convert %r" % value) def _convert_other(self, other): """Convert other to Decimal. @@ -2096,7 +2100,7 @@ class Context(object): prec - precision (for use in rounding, division, square roots..) rounding - rounding type. (how you round) _rounding_decision - ALWAYS_ROUND, NEVER_ROUND -- do you round? - trap_enablers - If trap_enablers[exception] = 1, then the exception is + traps - If traps[exception] = 1, then the exception is raised when it is caused. Otherwise, a value is substituted in. flags - When an exception is caused, flags[exception] is incremented. @@ -2110,13 +2114,15 @@ class Context(object): """ def __init__(self, prec=None, rounding=None, - trap_enablers=None, flags=None, + traps=None, flags=[], _rounding_decision=None, Emin=None, Emax=None, capitals=None, _clamp=0, _ignored_flags=[]): - if flags is None: - flags = dict.fromkeys(Signals, 0) + if not isinstance(flags, dict): + flags = dict([(s,s in flags) for s in Signals]) + if traps is not None and not isinstance(traps, dict): + traps = dict([(s,s in traps) for s in Signals]) for name, val in locals().items(): if val is None: setattr(self, name, copy.copy(getattr(DefaultContext, name))) @@ -2125,11 +2131,11 @@ class Context(object): del self.self def __repr__(self): - """Show the current context in readable form, not in a form for eval().""" + """Show the current context.""" s = [] - s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d' % vars(self)) - s.append('setflags=%r' % [f.__name__ for f, v in self.flags.items() if v]) - s.append('settraps=%r' % [t.__name__ for t, v in self.trap_enablers.items() if v]) + s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' % vars(self)) + s.append('flags=[' + ', '.join([f.__name__ for f, v in self.flags.items() if v]) + ']') + s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']') return ', '.join(s) + ')' def clear_flags(self): @@ -2139,7 +2145,7 @@ class Context(object): def copy(self): """Returns a copy from self.""" - nc = Context(self.prec, self.rounding, self.trap_enablers, self.flags, + nc = Context(self.prec, self.rounding, self.traps, self.flags, self._rounding_decision, self.Emin, self.Emax, self.capitals, self._clamp, self._ignored_flags) return nc @@ -2159,7 +2165,7 @@ class Context(object): return error().handle(self, *args) self.flags[error] += 1 - if not self.trap_enablers[error]: + if not self.traps[error]: #The errors define how to handle themselves. return condition().handle(self, *args) @@ -2946,13 +2952,10 @@ def _isnan(num): # The default context prototype used by Context() # Is mutable, so than new contexts can have different default values -_default_traps = dict.fromkeys(Signals, 0) -_default_traps.update({DivisionByZero:1, Overflow:1, InvalidOperation:1}) - DefaultContext = Context( prec=28, rounding=ROUND_HALF_EVEN, - trap_enablers=_default_traps, - flags=None, + traps=[DivisionByZero, Overflow, InvalidOperation], + flags=[], _rounding_decision=ALWAYS_ROUND, Emax=DEFAULT_MAX_EXPONENT, Emin=DEFAULT_MIN_EXPONENT, @@ -2964,20 +2967,17 @@ DefaultContext = Context( # contexts and be able to reproduce results from other implementations # of the spec. -_basic_traps = dict.fromkeys(Signals, 1) -_basic_traps.update({Inexact:0, Rounded:0, Subnormal:0}) - BasicContext = Context( prec=9, rounding=ROUND_HALF_UP, - trap_enablers=_basic_traps, - flags=None, + traps=[DivisionByZero, Overflow, InvalidOperation, Clamped, Underflow], + flags=[], _rounding_decision=ALWAYS_ROUND, ) ExtendedContext = Context( prec=9, rounding=ROUND_HALF_EVEN, - trap_enablers=dict.fromkeys(Signals, 0), - flags=None, + traps=[], + flags=[], _rounding_decision=ALWAYS_ROUND, ) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index dc202f3..6354063 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -38,7 +38,7 @@ import random # Tests are built around these assumed context defaults DefaultContext.prec=9 DefaultContext.rounding=ROUND_HALF_EVEN -DefaultContext.trap_enablers=dict.fromkeys(Signals, 0) +DefaultContext.traps=dict.fromkeys(Signals, 0) setcontext(DefaultContext) @@ -105,8 +105,8 @@ class DecimalTest(unittest.TestCase): def setUp(self): global dir self.context = Context() - for key in DefaultContext.trap_enablers.keys(): - DefaultContext.trap_enablers[key] = 1 + for key in DefaultContext.traps.keys(): + DefaultContext.traps[key] = 1 self.ignore_list = ['#'] # Basically, a # means return NaN InvalidOperation. # Different from a sNaN in trim @@ -120,8 +120,8 @@ class DecimalTest(unittest.TestCase): def tearDown(self): """Cleaning up enviroment.""" # leaving context in original state - for key in DefaultContext.trap_enablers.keys(): - DefaultContext.trap_enablers[key] = 0 + for key in DefaultContext.traps.keys(): + DefaultContext.traps[key] = 0 return def eval_file(self, file): @@ -205,9 +205,9 @@ class DecimalTest(unittest.TestCase): theirexceptions = [ErrorNames[x.lower()] for x in exceptions] for exception in Signals: - self.context.trap_enablers[exception] = 1 #Catch these bugs... + self.context.traps[exception] = 1 #Catch these bugs... for exception in theirexceptions: - self.context.trap_enablers[exception] = 0 + self.context.traps[exception] = 0 for i, val in enumerate(valstemp): if val.count("'") % 2 == 1: quote = 1 - quote @@ -221,7 +221,7 @@ class DecimalTest(unittest.TestCase): if fname in ('to_sci_string', 'to_eng_string'): if EXTENDEDERRORTEST: for error in theirexceptions: - self.context.trap_enablers[error] = 1 + self.context.traps[error] = 1 try: funct(self.context.create_decimal(v)) except error: @@ -231,7 +231,7 @@ class DecimalTest(unittest.TestCase): (e, s, error)) else: self.fail("Did not raise %s in %s" % (error, s)) - self.context.trap_enablers[error] = 0 + self.context.traps[error] = 0 v = self.context.create_decimal(v) else: v = Decimal(v) @@ -241,7 +241,7 @@ class DecimalTest(unittest.TestCase): if EXTENDEDERRORTEST and fname not in ('to_sci_string', 'to_eng_string'): for error in theirexceptions: - self.context.trap_enablers[error] = 1 + self.context.traps[error] = 1 try: funct(*vals) except error: @@ -251,7 +251,7 @@ class DecimalTest(unittest.TestCase): (e, s, error)) else: self.fail("Did not raise %s in %s" % (error, s)) - self.context.trap_enablers[error] = 0 + self.context.traps[error] = 0 try: result = str(funct(*vals)) if fname == 'same_quantum': @@ -263,7 +263,7 @@ class DecimalTest(unittest.TestCase): raise myexceptions = self.getexceptions() - self.resetflags() + self.context.clear_flags() myexceptions.sort() theirexceptions.sort() @@ -282,10 +282,6 @@ class DecimalTest(unittest.TestCase): L.append(exception) return L - def resetflags(self): - for exception in Signals: - self.context.flags[exception] = 0 - def change_precision(self, prec): self.context.prec = prec def change_rounding_method(self, rounding): -- cgit v0.12