mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Work through several open todos:
* Added test for pickling contexts * Renamed ExceptionList to Signals (to match wording in the spec) * Simplified Context constructor by allowing flags=None to automatically generate a zeroed-out flags dictionary. * inlined _convertString() which was used only once * _rounding_decision is private, so excluded its contants from __all__. * added an XXX comment with concerns about subclassing signals results in a deviation from the spec (maybe important, maybe not). * Taught the test_suite to determine its own directory (modeled after code in regrtest.py). Enables it to be run when the current directory is not the test directory. * Added a clear_flags() method to the Context API to make it easier to do a common operation with flags. * Fixed the trap_enablers defaults in BasicDefaultContext to match the spec.
This commit is contained in:
parent
41d13f61d2
commit
d9c0a7ae94
2 changed files with 55 additions and 50 deletions
|
@ -9,13 +9,8 @@
|
||||||
|
|
||||||
|
|
||||||
# Todo:
|
# 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
|
# 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
|
# Constants for use in setting up contexts
|
||||||
'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING',
|
'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING',
|
||||||
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',
|
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',
|
||||||
'NEVER_ROUND', 'ALWAYS_ROUND',
|
'Signals', # <-- Used for building trap/flag dictionaries
|
||||||
'ExceptionList', # <-- Used for building trap/flag dictionaries
|
|
||||||
|
|
||||||
# Functions for manipulating contexts
|
# Functions for manipulating contexts
|
||||||
'setcontext', 'getcontext',
|
'setcontext', 'getcontext',
|
||||||
|
@ -244,6 +238,12 @@ class InvalidOperation(DecimalException):
|
||||||
return Decimal( (args[1]._sign, args[1]._int, 'n') )
|
return Decimal( (args[1]._sign, args[1]._int, 'n') )
|
||||||
return NaN
|
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):
|
class ConversionSyntax(InvalidOperation):
|
||||||
"""Trying to convert badly formed string.
|
"""Trying to convert badly formed string.
|
||||||
|
|
||||||
|
@ -410,8 +410,8 @@ def _filterfunc(obj):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#ExceptionList holds the exceptions
|
#Signals holds the exceptions
|
||||||
ExceptionList = filter(_filterfunc, globals().values())
|
Signals = filter(_filterfunc, globals().values())
|
||||||
|
|
||||||
del _filterfunc
|
del _filterfunc
|
||||||
|
|
||||||
|
@ -492,7 +492,10 @@ class Decimal(object):
|
||||||
self._sign = sign
|
self._sign = sign
|
||||||
self._int = tuple(map(int, diag)) #Diagnostic info
|
self._int = tuple(map(int, diag)) #Diagnostic info
|
||||||
return
|
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
|
return
|
||||||
|
|
||||||
# tuple/list conversion (possibly from as_tuple())
|
# tuple/list conversion (possibly from as_tuple())
|
||||||
|
@ -594,19 +597,6 @@ class Decimal(object):
|
||||||
return other
|
return other
|
||||||
return 0
|
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):
|
def __nonzero__(self):
|
||||||
"""Is the number non-zero?
|
"""Is the number non-zero?
|
||||||
|
|
||||||
|
@ -1433,8 +1423,6 @@ class Decimal(object):
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
"""Converts self to a int, truncating if necessary."""
|
"""Converts self to a int, truncating if necessary."""
|
||||||
# XXX This should be implemented in terms of tested
|
|
||||||
# functions in the standard
|
|
||||||
if self._isnan():
|
if self._isnan():
|
||||||
context = getcontext()
|
context = getcontext()
|
||||||
return context._raise_error(InvalidContext)
|
return context._raise_error(InvalidContext)
|
||||||
|
@ -2115,6 +2103,8 @@ class Decimal(object):
|
||||||
return self # My components are also immutable
|
return self # My components are also immutable
|
||||||
return self.__class__(str(self))
|
return self.__class__(str(self))
|
||||||
|
|
||||||
|
##### Context class ###########################################
|
||||||
|
|
||||||
|
|
||||||
# get rounding method function:
|
# get rounding method function:
|
||||||
rounding_functions = [name for name in Decimal.__dict__.keys() if name.startswith('_round_')]
|
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,
|
Emin=DEFAULT_MIN_EXPONENT, Emax=DEFAULT_MAX_EXPONENT,
|
||||||
capitals=1, _clamp=0,
|
capitals=1, _clamp=0,
|
||||||
_ignored_flags=[]):
|
_ignored_flags=[]):
|
||||||
|
if flags is None:
|
||||||
|
flags = dict.fromkeys(Signals, 0)
|
||||||
DefaultLock.acquire()
|
DefaultLock.acquire()
|
||||||
for name, val in locals().items():
|
for name, val in locals().items():
|
||||||
if val is None:
|
if val is None:
|
||||||
|
@ -2161,13 +2153,17 @@ class Context(object):
|
||||||
DefaultLock.release()
|
DefaultLock.release()
|
||||||
del self.self
|
del self.self
|
||||||
|
|
||||||
|
def clear_flags(self):
|
||||||
|
"""Reset all flags to zero"""
|
||||||
|
for flag in self.flags:
|
||||||
|
self.flag = 0
|
||||||
|
|
||||||
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.trap_enablers, 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
|
||||||
__copy__ = copy
|
|
||||||
|
|
||||||
def _raise_error(self, error, explanation = None, *args):
|
def _raise_error(self, error, explanation = None, *args):
|
||||||
"""Handles an error
|
"""Handles an error
|
||||||
|
@ -2192,7 +2188,7 @@ class Context(object):
|
||||||
|
|
||||||
def _ignore_all_flags(self):
|
def _ignore_all_flags(self):
|
||||||
"""Ignore all flags, if they are raised"""
|
"""Ignore all flags, if they are raised"""
|
||||||
return self._ignore_flags(*ExceptionList)
|
return self._ignore_flags(*Signals)
|
||||||
|
|
||||||
def _ignore_flags(self, *flags):
|
def _ignore_flags(self, *flags):
|
||||||
"""Ignore the flags, if they are raised"""
|
"""Ignore the flags, if they are raised"""
|
||||||
|
@ -2959,20 +2955,16 @@ def isnan(num):
|
||||||
|
|
||||||
##### Setup Specific Contexts ################################
|
##### Setup Specific Contexts ################################
|
||||||
|
|
||||||
def _zero_exceptions():
|
_basic_traps = dict.fromkeys(Signals, 1)
|
||||||
"Helper function mapping all exceptions to zero."
|
_basic_traps.update({Inexact:0, Rounded:0, Subnormal:0})
|
||||||
d = {}
|
|
||||||
for exception in ExceptionList:
|
|
||||||
d[exception] = 0
|
|
||||||
return d
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
DefaultContext = Context(
|
DefaultContext = Context(
|
||||||
prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN,
|
prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN,
|
||||||
trap_enablers=_zero_exceptions(),
|
trap_enablers=dict.fromkeys(Signals, 0),
|
||||||
flags=_zero_exceptions(),
|
flags=None,
|
||||||
_rounding_decision=ALWAYS_ROUND,
|
_rounding_decision=ALWAYS_ROUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2981,25 +2973,22 @@ 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 = _zero_exceptions()
|
|
||||||
_basic_traps.update({Inexact:1, Rounded:1, Subnormal:1})
|
|
||||||
|
|
||||||
BasicDefaultContext = Context(
|
BasicDefaultContext = Context(
|
||||||
prec=9, rounding=ROUND_HALF_UP,
|
prec=9, rounding=ROUND_HALF_UP,
|
||||||
trap_enablers=_basic_traps,
|
trap_enablers=_basic_traps,
|
||||||
flags=_zero_exceptions(),
|
flags=None,
|
||||||
_rounding_decision=ALWAYS_ROUND,
|
_rounding_decision=ALWAYS_ROUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
ExtendedDefaultContext = Context(
|
ExtendedDefaultContext = Context(
|
||||||
prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN,
|
prec=SINGLE_PRECISION, rounding=ROUND_HALF_EVEN,
|
||||||
trap_enablers=_zero_exceptions(),
|
trap_enablers=dict.fromkeys(Signals, 0),
|
||||||
flags=_zero_exceptions(),
|
flags=None,
|
||||||
_rounding_decision=ALWAYS_ROUND,
|
_rounding_decision=ALWAYS_ROUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
##### Useful Constants (internal use only######################
|
##### Useful Constants (internal use only) ####################
|
||||||
|
|
||||||
#Reusable defaults
|
#Reusable defaults
|
||||||
Inf = Decimal('Inf')
|
Inf = Decimal('Inf')
|
||||||
|
|
|
@ -35,7 +35,12 @@ from test.test_support import TestSkipped, run_unittest, run_doctest, is_resourc
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
TESTDATADIR = 'decimaltestdata'
|
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)
|
skip_expected = not os.path.isdir(dir)
|
||||||
|
|
||||||
|
@ -190,7 +195,7 @@ class DecimalTest(unittest.TestCase):
|
||||||
quote = 0
|
quote = 0
|
||||||
theirexceptions = [ErrorNames[x.lower()] for x in exceptions]
|
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...
|
self.context.trap_enablers[exception] = 1 #Catch these bugs...
|
||||||
for exception in theirexceptions:
|
for exception in theirexceptions:
|
||||||
self.context.trap_enablers[exception] = 0
|
self.context.trap_enablers[exception] = 0
|
||||||
|
@ -212,7 +217,7 @@ class DecimalTest(unittest.TestCase):
|
||||||
funct(self.context.create_decimal(v))
|
funct(self.context.create_decimal(v))
|
||||||
except error:
|
except error:
|
||||||
pass
|
pass
|
||||||
except ExceptionList, e:
|
except Signals, e:
|
||||||
self.fail("Raised %s in %s when %s disabled" % \
|
self.fail("Raised %s in %s when %s disabled" % \
|
||||||
(e, s, error))
|
(e, s, error))
|
||||||
else:
|
else:
|
||||||
|
@ -232,7 +237,7 @@ class DecimalTest(unittest.TestCase):
|
||||||
funct(*vals)
|
funct(*vals)
|
||||||
except error:
|
except error:
|
||||||
pass
|
pass
|
||||||
except ExceptionList, e:
|
except Signals, e:
|
||||||
self.fail("Raised %s in %s when %s disabled" % \
|
self.fail("Raised %s in %s when %s disabled" % \
|
||||||
(e, s, error))
|
(e, s, error))
|
||||||
else:
|
else:
|
||||||
|
@ -242,7 +247,7 @@ class DecimalTest(unittest.TestCase):
|
||||||
result = str(funct(*vals))
|
result = str(funct(*vals))
|
||||||
if fname == 'same_quantum':
|
if fname == 'same_quantum':
|
||||||
result = str(int(eval(result))) # 'True', 'False' -> '1', '0'
|
result = str(int(eval(result))) # 'True', 'False' -> '1', '0'
|
||||||
except ExceptionList, error:
|
except Signals, error:
|
||||||
self.fail("Raised %s in %s" % (error, s))
|
self.fail("Raised %s in %s" % (error, s))
|
||||||
except: #Catch any error long enough to state the test case.
|
except: #Catch any error long enough to state the test case.
|
||||||
print "ERROR:", s
|
print "ERROR:", s
|
||||||
|
@ -263,13 +268,13 @@ class DecimalTest(unittest.TestCase):
|
||||||
|
|
||||||
def getexceptions(self):
|
def getexceptions(self):
|
||||||
L = []
|
L = []
|
||||||
for exception in ExceptionList:
|
for exception in Signals:
|
||||||
if self.context.flags[exception]:
|
if self.context.flags[exception]:
|
||||||
L.append(exception)
|
L.append(exception)
|
||||||
return L
|
return L
|
||||||
|
|
||||||
def resetflags(self):
|
def resetflags(self):
|
||||||
for exception in ExceptionList:
|
for exception in Signals:
|
||||||
self.context.flags[exception] = 0
|
self.context.flags[exception] = 0
|
||||||
|
|
||||||
def change_precision(self, prec):
|
def change_precision(self, prec):
|
||||||
|
@ -1046,6 +1051,16 @@ class DecimalPythonAPItests(unittest.TestCase):
|
||||||
e = pickle.loads(p)
|
e = pickle.loads(p)
|
||||||
self.assertEqual(d, e)
|
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):
|
def test_main(arith=False, verbose=None):
|
||||||
""" Execute the tests.
|
""" Execute the tests.
|
||||||
|
|
||||||
|
@ -1059,6 +1074,7 @@ def test_main(arith=False, verbose=None):
|
||||||
DecimalUseOfContextTest,
|
DecimalUseOfContextTest,
|
||||||
DecimalUsabilityTest,
|
DecimalUsabilityTest,
|
||||||
DecimalPythonAPItests,
|
DecimalPythonAPItests,
|
||||||
|
ContextAPItests,
|
||||||
]
|
]
|
||||||
|
|
||||||
if arith or is_resource_enabled('decimal'):
|
if arith or is_resource_enabled('decimal'):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue