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.
This commit is contained in:
Raymond Hettinger 2004-07-10 14:14:37 +00:00
parent d9dfe0213f
commit bf4406971c
3 changed files with 54 additions and 58 deletions

View File

@ -112,7 +112,7 @@ precision, rounding, or trap enablers:
>>> from decimal import * >>> from decimal import *
>>> getcontext() >>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
setflags=[], settraps=[]) capitals=1, flags=[], traps=[])
>>> getcontext().prec = 7 >>> getcontext().prec = 7
\end{verbatim} \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)
>>> myothercontext >>> myothercontext
Context(prec=60, rounding=ROUND_HALF_DOWN, Emin=-999999999, Emax=999999999, Context(prec=60, rounding=ROUND_HALF_DOWN, Emin=-999999999, Emax=999999999,
setflags=[], settraps=[]) capitals=1, flags=[], traps=[])
>>> ExtendedContext >>> ExtendedContext
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
setflags=[], settraps=[]) capitals=1, flags=[], traps=[])
>>> setcontext(myothercontext) >>> setcontext(myothercontext)
>>> Decimal(1) / Decimal(7) >>> Decimal(1) / Decimal(7)
Decimal("0.142857142857142857142857142857142857142857142857142857142857") Decimal("0.142857142857142857142857142857142857142857142857142857142857")
@ -236,21 +236,21 @@ clear the flags before each set of monitored computations by using the
Decimal("3.14159292") Decimal("3.14159292")
>>> getcontext() >>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
setflags=['Inexact', 'Rounded'], settraps=[]) capitals=1, flags=[Inexact, Rounded], traps=[])
\end{verbatim} \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 \constant{Pi} was rounded (digits beyond the context precision were thrown
away) and that the result is inexact (some of the discarded digits were away) and that the result is inexact (some of the discarded digits were
non-zero). 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: field of a context:
\begin{verbatim} \begin{verbatim}
>>> Decimal(1) / Decimal(0) >>> Decimal(1) / Decimal(0)
Decimal("Infinity") Decimal("Infinity")
>>> getcontext().trap_enablers[DivisionByZero] = 1 >>> getcontext().traps[DivisionByZero] = 1
>>> Decimal(1) / Decimal(0) >>> Decimal(1) / Decimal(0)
Traceback (most recent call last): 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} \begin{verbatim}
>>> getcontext.clear_flags() >>> getcontext.clear_flags()
>>> for sig in getcontext().trap_enablers: >>> for sig in getcontext().traps:
... getcontext().trap_enablers[sig] = 1 ... getcontext().traps[sig] = 1
>>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0}) >>> getcontext().traps.update({Rounded:0, Inexact:0, Subnormal:0})
>>> getcontext() >>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
setflags=[], settraps=['Clamped', 'Underflow', 'InvalidOperation', capitals=1, flags=[], traps=[Clamped, Underflow,
'DivisionByZero', 'Overflow']) InvalidOperation, DivisionByZero, Overflow])
\end{verbatim} \end{verbatim}
Applications typically set the context once at the beginning of a program 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 In addition to the three supplied contexts, new contexts can be created
with the \class{Context} constructor. 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} flags=None, Emin=None, Emax=None, capitals=1}
Creates a new context. If a field is not specified or is \constant{None}, 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 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_HALF_UP} (away from zero), or
\constant{ROUND_UP} (away from zero). \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}. to either \constant{0} or \constant{1}.
The \var{Emin} and \var{Emax} fields are integers specifying the outer 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 # Set applicationwide defaults for all threads about to be launched
DefaultContext.prec=12 DefaultContext.prec=12
DefaultContext.rounding=ROUND_DOWN DefaultContext.rounding=ROUND_DOWN
DefaultContext.trap_enablers=dict.fromkeys(Signals, 0) DefaultContext.traps=dict.fromkeys(Signals, 0)
setcontext(DefaultContext) setcontext(DefaultContext)
# Now start all of the threads # Now start all of the threads

View File

@ -70,7 +70,7 @@ NaN
-Infinity -Infinity
>>> print dig / 0 >>> print dig / 0
Infinity Infinity
>>> getcontext().trap_enablers[DivisionByZero] = 1 >>> getcontext().traps[DivisionByZero] = 1
>>> print dig / 0 >>> print dig / 0
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -78,12 +78,12 @@ Traceback (most recent call last):
... ...
DivisionByZero: x / 0 DivisionByZero: x / 0
>>> c = Context() >>> c = Context()
>>> c.trap_enablers[InvalidOperation] = 0 >>> c.traps[InvalidOperation] = 0
>>> print c.flags[InvalidOperation] >>> print c.flags[InvalidOperation]
0 0
>>> c.divide(Decimal(0), Decimal(0)) >>> c.divide(Decimal(0), Decimal(0))
Decimal("NaN") Decimal("NaN")
>>> c.trap_enablers[InvalidOperation] = 1 >>> c.traps[InvalidOperation] = 1
>>> print c.flags[InvalidOperation] >>> print c.flags[InvalidOperation]
1 1
>>> c.flags[InvalidOperation] = 0 >>> c.flags[InvalidOperation] = 0
@ -98,7 +98,7 @@ InvalidOperation: 0 / 0
>>> print c.flags[InvalidOperation] >>> print c.flags[InvalidOperation]
1 1
>>> c.flags[InvalidOperation] = 0 >>> c.flags[InvalidOperation] = 0
>>> c.trap_enablers[InvalidOperation] = 0 >>> c.traps[InvalidOperation] = 0
>>> print c.divide(Decimal(0), Decimal(0)) >>> print c.divide(Decimal(0), Decimal(0))
NaN NaN
>>> print c.flags[InvalidOperation] >>> print c.flags[InvalidOperation]
@ -495,7 +495,11 @@ class Decimal(object):
self._int = value._int self._int = value._int
return 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): def _convert_other(self, other):
"""Convert other to Decimal. """Convert other to Decimal.
@ -2096,7 +2100,7 @@ class Context(object):
prec - precision (for use in rounding, division, square roots..) prec - precision (for use in rounding, division, square roots..)
rounding - rounding type. (how you round) rounding - rounding type. (how you round)
_rounding_decision - ALWAYS_ROUND, NEVER_ROUND -- do 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 raised when it is caused. Otherwise, a value is
substituted in. substituted in.
flags - When an exception is caused, flags[exception] is incremented. flags - When an exception is caused, flags[exception] is incremented.
@ -2110,13 +2114,15 @@ class Context(object):
""" """
def __init__(self, prec=None, rounding=None, def __init__(self, prec=None, rounding=None,
trap_enablers=None, flags=None, traps=None, flags=[],
_rounding_decision=None, _rounding_decision=None,
Emin=None, Emax=None, Emin=None, Emax=None,
capitals=None, _clamp=0, capitals=None, _clamp=0,
_ignored_flags=[]): _ignored_flags=[]):
if flags is None: if not isinstance(flags, dict):
flags = dict.fromkeys(Signals, 0) 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(): for name, val in locals().items():
if val is None: if val is None:
setattr(self, name, copy.copy(getattr(DefaultContext, name))) setattr(self, name, copy.copy(getattr(DefaultContext, name)))
@ -2125,11 +2131,11 @@ class Context(object):
del self.self del self.self
def __repr__(self): def __repr__(self):
"""Show the current context in readable form, not in a form for eval().""" """Show the current context."""
s = [] s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d' % vars(self)) s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' % vars(self))
s.append('setflags=%r' % [f.__name__ for f, v in self.flags.items() if v]) s.append('flags=[' + ', '.join([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('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']')
return ', '.join(s) + ')' return ', '.join(s) + ')'
def clear_flags(self): def clear_flags(self):
@ -2139,7 +2145,7 @@ class Context(object):
def copy(self): def copy(self):
"""Returns a copy from 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._rounding_decision, self.Emin, self.Emax,
self.capitals, self._clamp, self._ignored_flags) self.capitals, self._clamp, self._ignored_flags)
return nc return nc
@ -2159,7 +2165,7 @@ class Context(object):
return error().handle(self, *args) return error().handle(self, *args)
self.flags[error] += 1 self.flags[error] += 1
if not self.trap_enablers[error]: if not self.traps[error]:
#The errors define how to handle themselves. #The errors define how to handle themselves.
return condition().handle(self, *args) return condition().handle(self, *args)
@ -2946,13 +2952,10 @@ def _isnan(num):
# The default context prototype used by Context() # The default context prototype used by Context()
# Is mutable, so than new contexts can have different default values # 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( DefaultContext = Context(
prec=28, rounding=ROUND_HALF_EVEN, prec=28, rounding=ROUND_HALF_EVEN,
trap_enablers=_default_traps, traps=[DivisionByZero, Overflow, InvalidOperation],
flags=None, flags=[],
_rounding_decision=ALWAYS_ROUND, _rounding_decision=ALWAYS_ROUND,
Emax=DEFAULT_MAX_EXPONENT, Emax=DEFAULT_MAX_EXPONENT,
Emin=DEFAULT_MIN_EXPONENT, Emin=DEFAULT_MIN_EXPONENT,
@ -2964,20 +2967,17 @@ DefaultContext = Context(
# contexts and be able to reproduce results from other implementations # contexts and be able to reproduce results from other implementations
# of the spec. # of the spec.
_basic_traps = dict.fromkeys(Signals, 1)
_basic_traps.update({Inexact:0, Rounded:0, Subnormal:0})
BasicContext = Context( BasicContext = Context(
prec=9, rounding=ROUND_HALF_UP, prec=9, rounding=ROUND_HALF_UP,
trap_enablers=_basic_traps, traps=[DivisionByZero, Overflow, InvalidOperation, Clamped, Underflow],
flags=None, flags=[],
_rounding_decision=ALWAYS_ROUND, _rounding_decision=ALWAYS_ROUND,
) )
ExtendedContext = Context( ExtendedContext = Context(
prec=9, rounding=ROUND_HALF_EVEN, prec=9, rounding=ROUND_HALF_EVEN,
trap_enablers=dict.fromkeys(Signals, 0), traps=[],
flags=None, flags=[],
_rounding_decision=ALWAYS_ROUND, _rounding_decision=ALWAYS_ROUND,
) )

View File

@ -38,7 +38,7 @@ import random
# Tests are built around these assumed context defaults # Tests are built around these assumed context defaults
DefaultContext.prec=9 DefaultContext.prec=9
DefaultContext.rounding=ROUND_HALF_EVEN DefaultContext.rounding=ROUND_HALF_EVEN
DefaultContext.trap_enablers=dict.fromkeys(Signals, 0) DefaultContext.traps=dict.fromkeys(Signals, 0)
setcontext(DefaultContext) setcontext(DefaultContext)
@ -105,8 +105,8 @@ class DecimalTest(unittest.TestCase):
def setUp(self): def setUp(self):
global dir global dir
self.context = Context() self.context = Context()
for key in DefaultContext.trap_enablers.keys(): for key in DefaultContext.traps.keys():
DefaultContext.trap_enablers[key] = 1 DefaultContext.traps[key] = 1
self.ignore_list = ['#'] self.ignore_list = ['#']
# Basically, a # means return NaN InvalidOperation. # Basically, a # means return NaN InvalidOperation.
# Different from a sNaN in trim # Different from a sNaN in trim
@ -120,8 +120,8 @@ class DecimalTest(unittest.TestCase):
def tearDown(self): def tearDown(self):
"""Cleaning up enviroment.""" """Cleaning up enviroment."""
# leaving context in original state # leaving context in original state
for key in DefaultContext.trap_enablers.keys(): for key in DefaultContext.traps.keys():
DefaultContext.trap_enablers[key] = 0 DefaultContext.traps[key] = 0
return return
def eval_file(self, file): def eval_file(self, file):
@ -205,9 +205,9 @@ class DecimalTest(unittest.TestCase):
theirexceptions = [ErrorNames[x.lower()] for x in exceptions] theirexceptions = [ErrorNames[x.lower()] for x in exceptions]
for exception in Signals: 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: for exception in theirexceptions:
self.context.trap_enablers[exception] = 0 self.context.traps[exception] = 0
for i, val in enumerate(valstemp): for i, val in enumerate(valstemp):
if val.count("'") % 2 == 1: if val.count("'") % 2 == 1:
quote = 1 - quote quote = 1 - quote
@ -221,7 +221,7 @@ class DecimalTest(unittest.TestCase):
if fname in ('to_sci_string', 'to_eng_string'): if fname in ('to_sci_string', 'to_eng_string'):
if EXTENDEDERRORTEST: if EXTENDEDERRORTEST:
for error in theirexceptions: for error in theirexceptions:
self.context.trap_enablers[error] = 1 self.context.traps[error] = 1
try: try:
funct(self.context.create_decimal(v)) funct(self.context.create_decimal(v))
except error: except error:
@ -231,7 +231,7 @@ class DecimalTest(unittest.TestCase):
(e, s, error)) (e, s, error))
else: else:
self.fail("Did not raise %s in %s" % (error, s)) 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) v = self.context.create_decimal(v)
else: else:
v = Decimal(v) v = Decimal(v)
@ -241,7 +241,7 @@ class DecimalTest(unittest.TestCase):
if EXTENDEDERRORTEST and fname not in ('to_sci_string', 'to_eng_string'): if EXTENDEDERRORTEST and fname not in ('to_sci_string', 'to_eng_string'):
for error in theirexceptions: for error in theirexceptions:
self.context.trap_enablers[error] = 1 self.context.traps[error] = 1
try: try:
funct(*vals) funct(*vals)
except error: except error:
@ -251,7 +251,7 @@ class DecimalTest(unittest.TestCase):
(e, s, error)) (e, s, error))
else: else:
self.fail("Did not raise %s in %s" % (error, s)) self.fail("Did not raise %s in %s" % (error, s))
self.context.trap_enablers[error] = 0 self.context.traps[error] = 0
try: try:
result = str(funct(*vals)) result = str(funct(*vals))
if fname == 'same_quantum': if fname == 'same_quantum':
@ -263,7 +263,7 @@ class DecimalTest(unittest.TestCase):
raise raise
myexceptions = self.getexceptions() myexceptions = self.getexceptions()
self.resetflags() self.context.clear_flags()
myexceptions.sort() myexceptions.sort()
theirexceptions.sort() theirexceptions.sort()
@ -282,10 +282,6 @@ class DecimalTest(unittest.TestCase):
L.append(exception) L.append(exception)
return L return L
def resetflags(self):
for exception in Signals:
self.context.flags[exception] = 0
def change_precision(self, prec): def change_precision(self, prec):
self.context.prec = prec self.context.prec = prec
def change_rounding_method(self, rounding): def change_rounding_method(self, rounding):