blob: bc810b0bc83e04c8c1eda941b50951647f0a92da [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
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000013 def __enter__(self):
14 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000015 return next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000016 except StopIteration:
17 raise RuntimeError("generator didn't yield")
18
19 def __exit__(self, type, value, traceback):
20 if type is None:
21 try:
Georg Brandla18af4e2007-04-21 15:47:16 +000022 next(self.gen)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000023 except StopIteration:
24 return
25 else:
26 raise RuntimeError("generator didn't stop")
27 else:
28 try:
29 self.gen.throw(type, value, traceback)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000030 raise RuntimeError("generator didn't stop after throw()")
Guido van Rossumb940e112007-01-10 16:19:56 +000031 except StopIteration as exc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000032 # Suppress the exception *unless* it's the same exception that
33 # was passed to throw(). This prevents a StopIteration
34 # raised inside the "with" statement from being suppressed
35 return exc is not value
36 except:
37 # 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 #
44 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__
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000081 helper.__dict__ = func.__dict__
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000082 except:
83 pass
84 return helper
85
86
87@contextmanager
Thomas Wouters477c8d52006-05-27 19:21:47 +000088def nested(*managers):
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000089 """Support multiple context managers in a single with-statement.
90
91 Code like this:
92
93 with nested(A, B, C) as (X, Y, Z):
94 <body>
95
96 is equivalent to this:
97
98 with A as X:
99 with B as Y:
100 with C as Z:
101 <body>
102
103 """
104 exits = []
105 vars = []
Guido van Rossumf6694362006-03-10 02:28:35 +0000106 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000107 try:
Guido van Rossum04110fb2007-08-24 16:32:05 +0000108 for mgr in managers:
109 exit = mgr.__exit__
110 enter = mgr.__enter__
111 vars.append(enter())
112 exits.append(exit)
113 yield vars
114 except:
115 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000116 finally:
117 while exits:
118 exit = exits.pop()
119 try:
Guido van Rossumf6694362006-03-10 02:28:35 +0000120 if exit(*exc):
121 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000122 except:
123 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000124 if exc != (None, None, None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000125 # Don't rely on sys.exc_info() still containing
126 # the right information. Another exception may
127 # have been raised and caught by an exit method
128 raise exc[0], exc[1], exc[2]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000129
130
Thomas Wouters477c8d52006-05-27 19:21:47 +0000131class closing(object):
132 """Context to automatically close something at the end of a block.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000133
134 Code like this:
135
136 with closing(<module>.open(<arguments>)) as f:
137 <block>
138
139 is equivalent to this:
140
141 f = <module>.open(<arguments>)
142 try:
143 <block>
144 finally:
145 f.close()
146
147 """
Thomas Wouters477c8d52006-05-27 19:21:47 +0000148 def __init__(self, thing):
149 self.thing = thing
150 def __enter__(self):
151 return self.thing
152 def __exit__(self, *exc_info):
153 self.thing.close()