summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/decimal.py75
-rw-r--r--Lib/test/test_decimal.py30
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'):