[Python-Dev] Py2.5 issue: decimal context manager misimplemented, misdesigned, and misdocumented

Nick Coghlan ncoghlan at gmail.com
Wed Aug 30 13:11:52 CEST 2006


Raymond Hettinger wrote:
> I would like to see the changes to the decimal module reverted for the 
> Py2.5 release.

I believe you may be overreacting - I don't consider the current behaviour 
buggy and the module level API can be added later.

That said, the docstring is definitely wrong, and I can't find any unit tests 
for the feature (I thought there were some in test_with, but I appear to be 
mistaken).

> Currently, the code in the decimal module implements the context manager 
> as a separate class instead of incorporating it directly in 
> decimal.Context.  This makes the API unnecessarily complex and is not 
> pretty compared to the code it was intended to replace.

The removal of the __context__ method made it impossible to permit context 
objects to be used directly in with statements. Even when that was the case, 
the separate ContextManager class was necessary in order to correctly handle 
the restoration as context objects may be shared between threads or nested 
within a single thread [1]. The localcontext() function in PEP 343 does 
exactly the same thing - it merely uses a generator context instead of a 
direct implementation of __enter__ and __exit__.

The current syntax (the get_manager() method) can easily be made prettier in 
the future by adding a sugar function at the module level:

   def localcontext(ctx=None):
       if ctx is None: ctx = getcontext()
       return ctx.get_manager()

> Worse still, the implementation saves a reference to the context instead 
> of making a copy of it.  Remember decimal.Context objects are mutable -- 
> the current implementation does not fulfill its contract to restore the 
> context to its original state at the conclusion of the with-statement.

The implementation doesn't forget that. The context to restore is determined 
by calling getcontext() in the __enter__ method. The restored context has 
nothing to do with the context passed to the ContextManager constructor.

 >>> from decimal import getcontext()
 >>> getcontext().prec
28
 >>> with getcontext().get_manager() as ctx:
...   ctx.prec += 2
...
 >>> getcontext().prec
28

The only ways to break it are to call ContextManager directly with an existing 
context that someone else already has a reference to:

 >>> from decimal import getcontext()
 >>> getcontext().prec
28
 >>> with ContextManager(getcontext()) as ctx:
...   ctx.prec += 2
...
 >>> getcontext().prec
30

Or to deliberately reuse a ContextManager instance:

 >>> mgr = getcontext().get_manager()
 >>> with mgr as ctx:
...    ctx.prec += 2
...    print ctx.prec
...
32
 >>> with mgr as ctx:
...    ctx.prec += 2
...    print ctx.prec
...
34

Cheers,
Nick.

[1] In fact, get_manager() is merely a new name for the old __context__ 
method. This name was suggested by Neal after I initially called the method 
context_manager(), but was never separately discussed on python-dev (the 
original discussion occurred in one of the massive PEP 343 threads).

http://mail.python.org/pipermail/python-checkins/2006-May/052083.html


-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-Dev mailing list