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

Kristján Valur Jónsson kristjan at ccpgames.com
Thu Aug 8 14:14:52 CEST 2013


________________________________________
Frá: Ronald Oussoren [ronaldoussoren at mac.com]
> This appears to be simular to the mechanism in PEP 377 http://www.python.org/dev/peps/pep-0377/
>  which was rejected.
Indeed, it is similar, but simpler.  (I was unaware of Nick's PEP).  The PEP contains the fallacy that in case of __enter__ raising the SkipStatement (or in my case, ContextManagerExit) exception, that something needs to be assigned to the "as" body.   The proposal I'm making is to make it possible to do in a single context manager, what it is possible to do with two context managers currently:
@contextmanager errordude:
    1 // 0
    yield
@contextmanager handler:
    try:
        yield
    except ZeroDivisionError:
        pass
with handler as a, errordude as b:
    do_stuff(a, b)

do_stuff will be silently skipped, and b won't be assigned to.
I'm proposing a mechanism where the same could be done with a single context manager:
with combined as a, b:
    do_stuff(a, b)

Currently, you have programmatic capabilities (silent skipping of the statement) with two context managers that you don't have with a single one.  That's just plain odd.

I don't think the original objectsions to PEP 377 are valid.  My approach introduces very little added complexity.  The use case is "correctly being able to combine context managers".  It is a "completeness" argument too, that you can do with a single context manager what you can do with two nested ones.

>
> 2) The mechanism used in implementing ContextManagerExit above is easily extended to allowing a special context manager: None.  This is useful for having _optional_ context managers.  E.g. code like this:
>     with performance_timer():
>         do_work()
>
>     def performance_timer():
>         if profiling:
>             return accumulator
>         return None
>
> None becomes the trivial context manager and its __enter__ and __exit__ calls are skipped, along with their overhead.

 > How bad is the overhead of a trivial contextmanager (that is, one with empty bodies for both
> __enter__ and __exit__)?
Valid question.  Let's try:

#testnone.py
import timeit
class trivial:
    def __enter__(self):
        pass
    def __exit__(self, a, b, c):
        pass
trivial = trivial()
print("trivial")
print(timeit.timeit("with trivial: pass", setup = "from __main__ import trivial"))
print("none")
print(timeit.timeit("with None: pass", setup = "from __main__ import trivial"))
print("trivial + sum")
print(timeit.timeit("with trivial: 1+1", setup = "from __main__ import trivial"))
print("none + sum")
print(timeit.timeit("with None: 1+1", setup = "from __main__ import trivial"))


yields:
trivial
0.786079668918435
none
0.07240212102423305
trivial + sum
0.9069962424632848
none + sum
0.11295328499933865

As you can see, the function call overhead is tremendously high.  Two complete function calls with bells and whistles are omitted by using the None context manager.



Ronald


More information about the Python-ideas mailing list