mirror of https://github.com/python/cpython
Backport of decimal module context management updates from rev 51694 to 2.5 release branch
This commit is contained in:
parent
f07b590d7e
commit
c48daf5bc4
|
@ -443,28 +443,29 @@ the \function{getcontext()} and \function{setcontext()} functions:
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
Beginning with Python 2.5, you can also use the \keyword{with} statement
|
Beginning with Python 2.5, you can also use the \keyword{with} statement
|
||||||
to temporarily change the active context. For example the following code
|
and the \function{localcontext()} function to temporarily change the
|
||||||
increases the current decimal precision by 2 places, performs a
|
active context.
|
||||||
calculation, and then automatically restores the previous context:
|
|
||||||
|
|
||||||
|
\begin{funcdesc}{localcontext}{\optional{c}}
|
||||||
|
Return a context manager that will set the current context for
|
||||||
|
the active thread to a copy of \var{c} on entry to the with-statement
|
||||||
|
and restore the previous context when exiting the with-statement. If
|
||||||
|
no context is specified, a copy of the current context is used.
|
||||||
|
\versionadded{2.5}
|
||||||
|
|
||||||
|
For example, the following code sets the current decimal precision
|
||||||
|
to 42 places, performs a calculation, and then automatically restores
|
||||||
|
the previous context:
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import decimal
|
from decimal import localcontext
|
||||||
|
|
||||||
with decimal.getcontext() as ctx:
|
with localcontext() as ctx:
|
||||||
ctx.prec += 2 # add 2 more digits of precision
|
ctx.prec = 42 # Perform a high precision calculation
|
||||||
calculate_something()
|
s = calculate_something()
|
||||||
|
s = +s # Round the final result back to the default precision
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
\end{funcdesc}
|
||||||
The context that's active in the body of the \keyword{with} statement is
|
|
||||||
a \emph{copy} of the context you provided to the \keyword{with}
|
|
||||||
statement, so modifying its attributes doesn't affect anything except
|
|
||||||
that temporary copy.
|
|
||||||
|
|
||||||
You can use any decimal context in a \keyword{with} statement, but if
|
|
||||||
you just want to make a temporary change to some aspect of the current
|
|
||||||
context, it's easiest to just use \function{getcontext()} as shown
|
|
||||||
above.
|
|
||||||
|
|
||||||
New contexts can also be created using the \class{Context} constructor
|
New contexts can also be created using the \class{Context} constructor
|
||||||
described below. In addition, the module provides three pre-made
|
described below. In addition, the module provides three pre-made
|
||||||
|
|
|
@ -683,22 +683,22 @@ with lock:
|
||||||
The lock is acquired before the block is executed and always released once
|
The lock is acquired before the block is executed and always released once
|
||||||
the block is complete.
|
the block is complete.
|
||||||
|
|
||||||
The \module{decimal} module's contexts, which encapsulate the desired
|
The new \function{localcontext()} function in the \module{decimal} module
|
||||||
precision and rounding characteristics for computations, provide a
|
makes it easy to save and restore the current decimal context, which
|
||||||
\method{context_manager()} method for getting a context manager:
|
encapsulates the desired precision and rounding characteristics for
|
||||||
|
computations:
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
import decimal
|
from decimal import Decimal, Context, localcontext
|
||||||
|
|
||||||
# Displays with default precision of 28 digits
|
# Displays with default precision of 28 digits
|
||||||
v1 = decimal.Decimal('578')
|
v = Decimal('578')
|
||||||
print v1.sqrt()
|
print v.sqrt()
|
||||||
|
|
||||||
ctx = decimal.Context(prec=16)
|
with localcontext(Context(prec=16)):
|
||||||
with ctx.context_manager():
|
|
||||||
# All code in this block uses a precision of 16 digits.
|
# All code in this block uses a precision of 16 digits.
|
||||||
# The original context is restored on exiting the block.
|
# The original context is restored on exiting the block.
|
||||||
print v1.sqrt()
|
print v.sqrt()
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\subsection{Writing Context Managers\label{context-managers}}
|
\subsection{Writing Context Managers\label{context-managers}}
|
||||||
|
|
|
@ -131,7 +131,7 @@ __all__ = [
|
||||||
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',
|
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',
|
||||||
|
|
||||||
# Functions for manipulating contexts
|
# Functions for manipulating contexts
|
||||||
'setcontext', 'getcontext'
|
'setcontext', 'getcontext', 'localcontext'
|
||||||
]
|
]
|
||||||
|
|
||||||
import copy as _copy
|
import copy as _copy
|
||||||
|
@ -458,6 +458,49 @@ else:
|
||||||
|
|
||||||
del threading, local # Don't contaminate the namespace
|
del threading, local # Don't contaminate the namespace
|
||||||
|
|
||||||
|
def localcontext(ctx=None):
|
||||||
|
"""Return a context manager for a copy of the supplied context
|
||||||
|
|
||||||
|
Uses a copy of the current context if no context is specified
|
||||||
|
The returned context manager creates a local decimal context
|
||||||
|
in a with statement:
|
||||||
|
def sin(x):
|
||||||
|
with localcontext() as ctx:
|
||||||
|
ctx.prec += 2
|
||||||
|
# Rest of sin calculation algorithm
|
||||||
|
# uses a precision 2 greater than normal
|
||||||
|
return +s # Convert result to normal precision
|
||||||
|
|
||||||
|
def sin(x):
|
||||||
|
with localcontext(ExtendedContext):
|
||||||
|
# Rest of sin calculation algorithm
|
||||||
|
# uses the Extended Context from the
|
||||||
|
# General Decimal Arithmetic Specification
|
||||||
|
return +s # Convert result to normal context
|
||||||
|
|
||||||
|
"""
|
||||||
|
# The below can't be included in the docstring until Python 2.6
|
||||||
|
# as the doctest module doesn't understand __future__ statements
|
||||||
|
"""
|
||||||
|
>>> from __future__ import with_statement
|
||||||
|
>>> print getcontext().prec
|
||||||
|
28
|
||||||
|
>>> with localcontext():
|
||||||
|
... ctx = getcontext()
|
||||||
|
... ctx.prec() += 2
|
||||||
|
... print ctx.prec
|
||||||
|
...
|
||||||
|
30
|
||||||
|
>>> with localcontext(ExtendedContext):
|
||||||
|
... print getcontext().prec
|
||||||
|
...
|
||||||
|
9
|
||||||
|
>>> print getcontext().prec
|
||||||
|
28
|
||||||
|
"""
|
||||||
|
if ctx is None: ctx = getcontext()
|
||||||
|
return _ContextManager(ctx)
|
||||||
|
|
||||||
|
|
||||||
##### Decimal class ###########################################
|
##### Decimal class ###########################################
|
||||||
|
|
||||||
|
@ -2173,23 +2216,14 @@ for name in rounding_functions:
|
||||||
|
|
||||||
del name, val, globalname, rounding_functions
|
del name, val, globalname, rounding_functions
|
||||||
|
|
||||||
class ContextManager(object):
|
class _ContextManager(object):
|
||||||
"""Helper class to simplify Context management.
|
"""Context manager class to support localcontext().
|
||||||
|
|
||||||
Sample usage:
|
|
||||||
|
|
||||||
with decimal.ExtendedContext:
|
|
||||||
s = ...
|
|
||||||
return +s # Convert result to normal precision
|
|
||||||
|
|
||||||
with decimal.getcontext() as ctx:
|
|
||||||
ctx.prec += 2
|
|
||||||
s = ...
|
|
||||||
return +s
|
|
||||||
|
|
||||||
|
Sets a copy of the supplied context in __enter__() and restores
|
||||||
|
the previous decimal context in __exit__()
|
||||||
"""
|
"""
|
||||||
def __init__(self, new_context):
|
def __init__(self, new_context):
|
||||||
self.new_context = new_context
|
self.new_context = new_context.copy()
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.saved_context = getcontext()
|
self.saved_context = getcontext()
|
||||||
setcontext(self.new_context)
|
setcontext(self.new_context)
|
||||||
|
@ -2248,9 +2282,6 @@ class Context(object):
|
||||||
s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.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 get_manager(self):
|
|
||||||
return ContextManager(self.copy())
|
|
||||||
|
|
||||||
def clear_flags(self):
|
def clear_flags(self):
|
||||||
"""Reset all flags to zero"""
|
"""Reset all flags to zero"""
|
||||||
for flag in self.flags:
|
for flag in self.flags:
|
||||||
|
|
|
@ -330,32 +330,6 @@ class LockContextTestCase(unittest.TestCase):
|
||||||
return True
|
return True
|
||||||
self.boilerPlate(lock, locked)
|
self.boilerPlate(lock, locked)
|
||||||
|
|
||||||
class DecimalContextTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
# XXX Somebody should write more thorough tests for this
|
|
||||||
|
|
||||||
def testBasic(self):
|
|
||||||
ctx = decimal.getcontext()
|
|
||||||
orig_context = ctx.copy()
|
|
||||||
try:
|
|
||||||
ctx.prec = save_prec = decimal.ExtendedContext.prec + 5
|
|
||||||
with decimal.ExtendedContext.get_manager():
|
|
||||||
self.assertEqual(decimal.getcontext().prec,
|
|
||||||
decimal.ExtendedContext.prec)
|
|
||||||
self.assertEqual(decimal.getcontext().prec, save_prec)
|
|
||||||
try:
|
|
||||||
with decimal.ExtendedContext.get_manager():
|
|
||||||
self.assertEqual(decimal.getcontext().prec,
|
|
||||||
decimal.ExtendedContext.prec)
|
|
||||||
1/0
|
|
||||||
except ZeroDivisionError:
|
|
||||||
self.assertEqual(decimal.getcontext().prec, save_prec)
|
|
||||||
else:
|
|
||||||
self.fail("Didn't raise ZeroDivisionError")
|
|
||||||
finally:
|
|
||||||
decimal.setcontext(orig_context)
|
|
||||||
|
|
||||||
|
|
||||||
# This is needed to make the test actually run under regrtest.py!
|
# This is needed to make the test actually run under regrtest.py!
|
||||||
def test_main():
|
def test_main():
|
||||||
run_suite(
|
run_suite(
|
||||||
|
|
|
@ -23,6 +23,7 @@ or Behaviour) to test each part, or without parameter to test both parts. If
|
||||||
you're working through IDLE, you can import this test module and call test_main()
|
you're working through IDLE, you can import this test module and call test_main()
|
||||||
with the corresponding argument.
|
with the corresponding argument.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import glob
|
import glob
|
||||||
|
@ -1064,6 +1065,32 @@ class ContextAPItests(unittest.TestCase):
|
||||||
self.assertNotEqual(id(c.flags), id(d.flags))
|
self.assertNotEqual(id(c.flags), id(d.flags))
|
||||||
self.assertNotEqual(id(c.traps), id(d.traps))
|
self.assertNotEqual(id(c.traps), id(d.traps))
|
||||||
|
|
||||||
|
class WithStatementTest(unittest.TestCase):
|
||||||
|
# Can't do these as docstrings until Python 2.6
|
||||||
|
# as doctest can't handle __future__ statements
|
||||||
|
|
||||||
|
def test_localcontext(self):
|
||||||
|
# Use a copy of the current context in the block
|
||||||
|
orig_ctx = getcontext()
|
||||||
|
with localcontext() as enter_ctx:
|
||||||
|
set_ctx = getcontext()
|
||||||
|
final_ctx = getcontext()
|
||||||
|
self.assert_(orig_ctx is final_ctx, 'did not restore context correctly')
|
||||||
|
self.assert_(orig_ctx is not set_ctx, 'did not copy the context')
|
||||||
|
self.assert_(set_ctx is enter_ctx, '__enter__ returned wrong context')
|
||||||
|
|
||||||
|
def test_localcontextarg(self):
|
||||||
|
# Use a copy of the supplied context in the block
|
||||||
|
orig_ctx = getcontext()
|
||||||
|
new_ctx = Context(prec=42)
|
||||||
|
with localcontext(new_ctx) as enter_ctx:
|
||||||
|
set_ctx = getcontext()
|
||||||
|
final_ctx = getcontext()
|
||||||
|
self.assert_(orig_ctx is final_ctx, 'did not restore context correctly')
|
||||||
|
self.assert_(set_ctx.prec == new_ctx.prec, 'did not set correct context')
|
||||||
|
self.assert_(new_ctx is not set_ctx, 'did not copy the context')
|
||||||
|
self.assert_(set_ctx is enter_ctx, '__enter__ returned wrong context')
|
||||||
|
|
||||||
def test_main(arith=False, verbose=None):
|
def test_main(arith=False, verbose=None):
|
||||||
""" Execute the tests.
|
""" Execute the tests.
|
||||||
|
|
||||||
|
@ -1084,6 +1111,7 @@ def test_main(arith=False, verbose=None):
|
||||||
DecimalPythonAPItests,
|
DecimalPythonAPItests,
|
||||||
ContextAPItests,
|
ContextAPItests,
|
||||||
DecimalTest,
|
DecimalTest,
|
||||||
|
WithStatementTest,
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue