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