[Python-ideas] Enhanced context managers with ContextManagerExit and None

Kristján Valur Jónsson kristjan at ccpgames.com
Tue Aug 13 13:55:44 CEST 2013




> -----Original Message-----

> From: Ronald Oussoren [mailto:ronaldoussoren at mac.com]

>

> Skipping the body makes it possible to introduce flow control in a context

> manager, which could make it harder to read code. That is, currently the

> body is either executed unless an exception is raised that raises an

> exception, while with your proposal the body might be skipped entirely.  I

> haven't formed an opinion yet on whether or not that is a problem, but PEP

> 343 (introduction of the with statement) references

> <http://blogs.msdn.com/b/oldnewthing/archive/2005/01/06/347666.aspx>

> which claims that hiding flow control in macros can be bad.

>



Perhaps my example was not clear enough.  The body will be skipped entirely if the __enter__() method raises an exception.  An outer context manager can then suppress this exception.

However, you cannot create a single context manager that does both.  This, to  me, was the most serious problem with the old nested() context manager: Nesting of two context managers could not be correctly done for arbitrary context managers



I am not advocating that people do create such context manager, but pointing out that this omission means that you cannot always combine two context managers into one and preserve the semantics because it is possible to do something with two nested context managers that you cannot achieve with a single one.  And this is my completeness argument.



Imagine if there were code that could only be written using two nested functions, and that the nested function could not be folded into the outer one without sacrificing semantic correctness?  I.e., you could do:

    foo = bar(spam(value))

but this:

   def ham(value):

        return bar(spam(value))

   foo  = ham(value)

would not be correct for all possible "bar" and "spam"?



As an example, here is what you can currently do in python (again, not recommending it but providing as an example)

class ContextManagerExit(): pass

@contextmanager

def if_a():

    try:

        yield

    except ContextManagerExit:

        pass

@contextmanager

def if_b(condition):

    if not condition:

        raise ContextManagerExit

    yield





with if_a(), if_b(condition):

    execute_code()  #this line is executed only if "condition" is True





In current python, it is impossible to create a combined context manager that does this:

if_c = nested(if_a(), if_b(condition))

with if_c:

    execute_code()  #A single context manager cannot both raise an exception from __enter__() _and_ have it supressed.



With my patch, "if_b()" is all that is needed, because ContextManagerExit is a special exception that is caught and suppressed by the interpreter.  And so, it becomes possible to nest arbitrary context managers without sacrificing semantic correctness.







Ø  The overhead is fairly high, although not enough that I'd worry a lot in my code. But then again, I don't build gaming backends or cloud-scale software :-)



Ø  That said, using None as the "do nothing" context manager is problematic as this could hide problems in users code. I'm sure that I'm not the only person that sometimes forgets to actually return a value from a function. Using that (missing, and hence implicitly None) return value as a context manager currently causes an exception that clearly points out a bug in my code and could silently do the wrong thing with this proposal, e.g:



Ø  def get_lock(obj):

Ø  lck = LockManager.get_lock_for_object(obj)

Ø  # oops forgot to return lck



Ø  with get_lock(42):

Ø  ...



Ø  Using a different singleton instead of None would avoid this drawback.



I think something like this is important for context managers that are used as "diagnostic" tools, e.g. timing related context managers that may be disabled in production without changing code.

But I agree that None is problematic for the reason you demonstrate, and did consider that.  I'm suggesting it here as a demonstration of the concept, and also to reduce the need for yet another built-in.  Perhaps the ellipsis could be used, that's everyone's favourite singleton :)



K
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20130813/c4386471/attachment-0001.html>


More information about the Python-ideas mailing list