blob: c26e27e924faa5098c0955cf938294d7dc957018 [file] [log] [blame]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00001"""Utilities for with-statement contexts. See PEP 343."""
2
3import sys
4
5__all__ = ["contextmanager", "nested", "closing"]
6
7class GeneratorContextManager(object):
8 """Helper for @contextmanager decorator."""
9
10 def __init__(self, gen):
11 self.gen = gen
12
13 def __context__(self):
14 return self
15
16 def __enter__(self):
17 try:
18 return self.gen.next()
19 except StopIteration:
20 raise RuntimeError("generator didn't yield")
21
22 def __exit__(self, type, value, traceback):
23 if type is None:
24 try:
25 self.gen.next()
26 except StopIteration:
27 return
28 else:
29 raise RuntimeError("generator didn't stop")
30 else:
31 try:
32 self.gen.throw(type, value, traceback)
Phillip J. Eby6edd2582006-03-25 00:28:24 +000033 raise RuntimeError("generator didn't stop after throw()")
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000034 except StopIteration:
Phillip J. Eby9444bd52006-04-03 20:05:05 +000035 # Supress the exception unless it's the same exception the
36 # was passed to throw().
37 return sys.exc_info()[1] is not value
Phillip J. Eby6edd2582006-03-25 00:28:24 +000038 except:
Phillip J. Ebyccc7bb42006-03-25 04:32:12 +000039 # only re-raise if it's *not* the exception that was
40 # passed to throw(), because __exit__() must not raise
41 # an exception unless __exit__() itself failed. But throw()
42 # has to raise the exception to signal propagation, so this
43 # fixes the impedance mismatch between the throw() protocol
44 # and the __exit__() protocol.
45 #
Phillip J. Eby6edd2582006-03-25 00:28:24 +000046 if sys.exc_info()[1] is not value:
47 raise
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000048
49
50def contextmanager(func):
51 """@contextmanager decorator.
52
53 Typical usage:
54
55 @contextmanager
56 def some_generator(<arguments>):
57 <setup>
58 try:
59 yield <value>
60 finally:
61 <cleanup>
62
63 This makes this:
64
65 with some_generator(<arguments>) as <variable>:
66 <body>
67
68 equivalent to this:
69
70 <setup>
71 try:
72 <variable> = <value>
73 <body>
74 finally:
75 <cleanup>
76
77 """
78 def helper(*args, **kwds):
79 return GeneratorContextManager(func(*args, **kwds))
80 try:
81 helper.__name__ = func.__name__
82 helper.__doc__ = func.__doc__
Phillip J. Eby35fd1422006-03-28 00:07:24 +000083 helper.__dict__ = func.__dict__
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000084 except:
85 pass
86 return helper
87
88
89@contextmanager
90def nested(*contexts):
91 """Support multiple context managers in a single with-statement.
92
93 Code like this:
94
95 with nested(A, B, C) as (X, Y, Z):
96 <body>
97
98 is equivalent to this:
99
100 with A as X:
101 with B as Y:
102 with C as Z:
103 <body>
104
105 """
106 exits = []
107 vars = []
Guido van Rossumf6694362006-03-10 02:28:35 +0000108 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000109 try:
110 try:
111 for context in contexts:
112 mgr = context.__context__()
113 exit = mgr.__exit__
114 enter = mgr.__enter__
115 vars.append(enter())
116 exits.append(exit)
117 yield vars
118 except:
119 exc = sys.exc_info()
120 finally:
121 while exits:
122 exit = exits.pop()
123 try:
Guido van Rossumf6694362006-03-10 02:28:35 +0000124 if exit(*exc):
125 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000126 except:
127 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000128 if exc != (None, None, None):
129 raise
130
131
132@contextmanager
133def closing(thing):
134 """Context manager to automatically close something at the end of a block.
135
136 Code like this:
137
138 with closing(<module>.open(<arguments>)) as f:
139 <block>
140
141 is equivalent to this:
142
143 f = <module>.open(<arguments>)
144 try:
145 <block>
146 finally:
147 f.close()
148
149 """
150 try:
151 yield thing
152 finally:
153 thing.close()