blob: d329087c974e6b25a67374a2b424d73d0b6f8b90 [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:
Guido van Rossumf6694362006-03-10 02:28:35 +000035 return True
Phillip J. Eby6edd2582006-03-25 00:28:24 +000036 except:
Phillip J. Ebyccc7bb42006-03-25 04:32:12 +000037 # only re-raise if it's *not* the exception that was
38 # passed to throw(), because __exit__() must not raise
39 # an exception unless __exit__() itself failed. But throw()
40 # has to raise the exception to signal propagation, so this
41 # fixes the impedance mismatch between the throw() protocol
42 # and the __exit__() protocol.
43 #
Phillip J. Eby6edd2582006-03-25 00:28:24 +000044 if sys.exc_info()[1] is not value:
45 raise
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000046
47
48def contextmanager(func):
49 """@contextmanager decorator.
50
51 Typical usage:
52
53 @contextmanager
54 def some_generator(<arguments>):
55 <setup>
56 try:
57 yield <value>
58 finally:
59 <cleanup>
60
61 This makes this:
62
63 with some_generator(<arguments>) as <variable>:
64 <body>
65
66 equivalent to this:
67
68 <setup>
69 try:
70 <variable> = <value>
71 <body>
72 finally:
73 <cleanup>
74
75 """
76 def helper(*args, **kwds):
77 return GeneratorContextManager(func(*args, **kwds))
78 try:
79 helper.__name__ = func.__name__
80 helper.__doc__ = func.__doc__
81 except:
82 pass
83 return helper
84
85
86@contextmanager
87def nested(*contexts):
88 """Support multiple context managers in a single with-statement.
89
90 Code like this:
91
92 with nested(A, B, C) as (X, Y, Z):
93 <body>
94
95 is equivalent to this:
96
97 with A as X:
98 with B as Y:
99 with C as Z:
100 <body>
101
102 """
103 exits = []
104 vars = []
Guido van Rossumf6694362006-03-10 02:28:35 +0000105 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000106 try:
107 try:
108 for context in contexts:
109 mgr = context.__context__()
110 exit = mgr.__exit__
111 enter = mgr.__enter__
112 vars.append(enter())
113 exits.append(exit)
114 yield vars
115 except:
116 exc = sys.exc_info()
117 finally:
118 while exits:
119 exit = exits.pop()
120 try:
Guido van Rossumf6694362006-03-10 02:28:35 +0000121 if exit(*exc):
122 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000123 except:
124 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000125 if exc != (None, None, None):
126 raise
127
128
129@contextmanager
130def closing(thing):
131 """Context manager to automatically close something at the end of a block.
132
133 Code like this:
134
135 with closing(<module>.open(<arguments>)) as f:
136 <block>
137
138 is equivalent to this:
139
140 f = <module>.open(<arguments>)
141 try:
142 <block>
143 finally:
144 f.close()
145
146 """
147 try:
148 yield thing
149 finally:
150 thing.close()