contextlib.py 4.01 KB
Newer Older
1 2 3 4
"""Utilities for with-statement contexts.  See PEP 343."""

import sys

5
__all__ = ["contextmanager", "nested", "closing"]
6

7 8
class GeneratorContextManager(object):
    """Helper for @contextmanager decorator."""
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

    def __init__(self, gen):
        self.gen = gen

    def __enter__(self):
        try:
            return self.gen.next()
        except StopIteration:
            raise RuntimeError("generator didn't yield")

    def __exit__(self, type, value, traceback):
        if type is None:
            try:
                self.gen.next()
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn't stop")
        else:
            try:
                self.gen.throw(type, value, traceback)
30
                raise RuntimeError("generator didn't stop after throw()")
31
            except StopIteration, exc:
32 33 34
                # Suppress the exception *unless* it's the same exception that
                # was passed to throw().  This prevents a StopIteration
                # raised inside the "with" statement from being suppressed
35
                return exc is not value
36
            except:
37 38 39 40 41 42 43
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                #
44 45
                if sys.exc_info()[1] is not value:
                    raise
46 47


48 49
def contextmanager(func):
    """@contextmanager decorator.
50 51 52

    Typical usage:

53
        @contextmanager
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
        def some_generator(<arguments>):
            <setup>
            try:
                yield <value>
            finally:
                <cleanup>

    This makes this:

        with some_generator(<arguments>) as <variable>:
            <body>

    equivalent to this:

        <setup>
        try:
            <variable> = <value>
            <body>
        finally:
            <cleanup>

    """
    def helper(*args, **kwds):
77
        return GeneratorContextManager(func(*args, **kwds))
78 79 80
    try:
        helper.__name__ = func.__name__
        helper.__doc__ = func.__doc__
81
        helper.__dict__ = func.__dict__
82 83 84 85 86
    except:
        pass
    return helper


87
@contextmanager
88
def nested(*managers):
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
    """Support multiple context managers in a single with-statement.

    Code like this:

        with nested(A, B, C) as (X, Y, Z):
            <body>

    is equivalent to this:

        with A as X:
            with B as Y:
                with C as Z:
                    <body>

    """
    exits = []
    vars = []
106
    exc = (None, None, None)
107 108
    try:
        try:
109
            for mgr in managers:
110 111 112 113 114 115 116 117 118 119 120
                exit = mgr.__exit__
                enter = mgr.__enter__
                vars.append(enter())
                exits.append(exit)
            yield vars
        except:
            exc = sys.exc_info()
    finally:
        while exits:
            exit = exits.pop()
            try:
121 122
                if exit(*exc):
                    exc = (None, None, None)
123 124 125
            except:
                exc = sys.exc_info()
        if exc != (None, None, None):
126 127 128 129
            # Don't rely on sys.exc_info() still containing
            # the right information. Another exception may
            # have been raised and caught by an exit method
            raise exc[0], exc[1], exc[2]
130 131


132 133
class closing(object):
    """Context to automatically close something at the end of a block.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148

    Code like this:

        with closing(<module>.open(<arguments>)) as f:
            <block>

    is equivalent to this:

        f = <module>.open(<arguments>)
        try:
            <block>
        finally:
            f.close()

    """
149 150 151 152 153 154
    def __init__(self, thing):
        self.thing = thing
    def __enter__(self):
        return self.thing
    def __exit__(self, *exc_info):
        self.thing.close()