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

import sys
Christian Heimes's avatar
Christian Heimes committed
4
from functools import wraps
5
from warnings import warn
6

7
__all__ = ["contextmanager", "closing"]
8 9 10 11 12 13 14 15 16

class GeneratorContextManager(object):
    """Helper for @contextmanager decorator."""

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

    def __enter__(self):
        try:
17
            return next(self.gen)
18 19 20 21 22 23
        except StopIteration:
            raise RuntimeError("generator didn't yield")

    def __exit__(self, type, value, traceback):
        if type is None:
            try:
24
                next(self.gen)
25 26 27 28 29
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn't stop")
        else:
30 31 32 33
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = type()
34 35
            try:
                self.gen.throw(type, value, traceback)
36
                raise RuntimeError("generator didn't stop after throw()")
37
            except StopIteration as exc:
38 39 40 41 42 43 44 45 46 47 48 49 50 51
                # 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
                return exc is not value
            except:
                # 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.
                #
                if sys.exc_info()[1] is not value:
                    raise
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81


def contextmanager(func):
    """@contextmanager decorator.

    Typical usage:

        @contextmanager
        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>

    """
Christian Heimes's avatar
Christian Heimes committed
82
    @wraps(func)
83 84 85 86 87
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper


88 89
class closing(object):
    """Context to automatically close something at the end of a block.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

    Code like this:

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

    is equivalent to this:

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

    """
105 106 107 108 109 110
    def __init__(self, thing):
        self.thing = thing
    def __enter__(self):
        return self.thing
    def __exit__(self, *exc_info):
        self.thing.close()