[Python-checkins] r51664 - in python/trunk: Doc/lib/libdecimal.tex Lib/decimal.py Lib/test/test_decimal.py

nick.coghlan python-checkins at python.org
Thu Aug 31 14:00:44 CEST 2006


Author: nick.coghlan
Date: Thu Aug 31 14:00:43 2006
New Revision: 51664

Modified:
   python/trunk/Doc/lib/libdecimal.tex
   python/trunk/Lib/decimal.py
   python/trunk/Lib/test/test_decimal.py
Log:
Fix the wrongheaded implementation of context management in the decimal module and add unit tests. (python-dev discussion is ongoing regarding what we do about Python 2.5)

Modified: python/trunk/Doc/lib/libdecimal.tex
==============================================================================
--- python/trunk/Doc/lib/libdecimal.tex	(original)
+++ python/trunk/Doc/lib/libdecimal.tex	Thu Aug 31 14:00:43 2006
@@ -435,26 +435,34 @@
 the \function{getcontext()} and \function{setcontext()} functions:
 
 \begin{funcdesc}{getcontext}{}
-  Return the current context for the active thread.                                          
+  Return the current context for the active thread.
 \end{funcdesc}            
 
 \begin{funcdesc}{setcontext}{c}
-  Set the current context for the active thread to \var{c}.                                          
+  Set the current context for the active thread to \var{c}.
 \end{funcdesc}  
 
 Beginning with Python 2.5, you can also use the \keyword{with} statement
-to temporarily change the active context. For example the following code
-increases the current decimal precision by 2 places, performs a
-calculation, and then automatically restores the previous context:
+to temporarily change the active context.
 
-\begin{verbatim}
-from __future__ import with_statement
-import decimal
-
-with decimal.getcontext() as ctx:
-    ctx.prec += 2   # add 2 more digits of precision
-    calculate_something()
+\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.
+
+  For example the following code increases the current decimal precision
+  by 2 places, performs a calculation, and then automatically restores
+  the previous context:
+\begin{verbatim}
+    from __future__ import with_statement
+    import decimal
+
+    with decimal.localcontext() as ctx:
+        ctx.prec += 2   # add 2 more digits of precision
+        s = calculate_something()
+    s = +s  # Round the final result back to the default precision
 \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}

Modified: python/trunk/Lib/decimal.py
==============================================================================
--- python/trunk/Lib/decimal.py	(original)
+++ python/trunk/Lib/decimal.py	Thu Aug 31 14:00:43 2006
@@ -130,8 +130,11 @@
     'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING',
     'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',
 
+    # helper for context management
+    'ContextManager',
+
     # Functions for manipulating contexts
-    'setcontext', 'getcontext'
+    'setcontext', 'getcontext', 'localcontext'
 ]
 
 import copy as _copy
@@ -458,6 +461,49 @@
 
     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().copy()
+    return ContextManager(ctx.copy())
+
 
 ##### Decimal class ###########################################
 
@@ -2174,20 +2220,27 @@
 del name, val, globalname, rounding_functions
 
 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 the supplied context in __enter__() and restores
+      the previous decimal context in __exit__()
 
     """
+    # The below can't be included in the docstring until Python 2.6
+    # as the doctest module doesn't understand __future__ statements
+    """
+    Sample usage:
+    >>> from __future__ import with_statement
+    >>> print getcontext().prec
+    28
+    >>> ctx = Context(prec=15)
+    >>> with ContextManager(ctx):
+    ...     print getcontext().prec
+    ...
+    15
+    >>> print getcontext().prec
+    28
+    """
     def __init__(self, new_context):
         self.new_context = new_context
     def __enter__(self):
@@ -2248,9 +2301,6 @@
         s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']')
         return ', '.join(s) + ')'
 
-    def get_manager(self):
-        return ContextManager(self.copy())
-
     def clear_flags(self):
         """Reset all flags to zero"""
         for flag in self.flags:

Modified: python/trunk/Lib/test/test_decimal.py
==============================================================================
--- python/trunk/Lib/test/test_decimal.py	(original)
+++ python/trunk/Lib/test/test_decimal.py	Thu Aug 31 14:00:43 2006
@@ -23,6 +23,7 @@
 you're working through IDLE, you can import this test module and call test_main()
 with the corresponding argument.
 """
+from __future__ import with_statement
 
 import unittest
 import glob
@@ -1064,6 +1065,43 @@
         self.assertNotEqual(id(c.flags), id(d.flags))
         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_ContextManager(self):
+        # The basic context manager uses the supplied context
+        # without making a copy of it
+        orig_ctx = getcontext()
+        new_ctx = Context()
+        with ContextManager(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_(new_ctx is set_ctx, 'did not set correct context')
+        self.assert_(set_ctx is enter_ctx, '__enter__ returned wrong context')
+
+    def test_localcontext(self):
+        # The helper function makes a copy of the supplied context
+        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):
+        # The helper function makes a copy of the supplied context
+        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):
     """ Execute the tests.
 
@@ -1084,6 +1122,7 @@
         DecimalPythonAPItests,
         ContextAPItests,
         DecimalTest,
+        WithStatementTest,
     ]
 
     try:


More information about the Python-checkins mailing list