blob: c4fab1bbfa86333df429016c3fb96d4c3aeaf794 [file] [log] [blame]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00001"""Utilities for with-statement contexts. See PEP 343."""
2
3import sys
4
Nick Coghlanafd5e632006-05-03 13:02:47 +00005__all__ = ["contextmanager", "nested", "closing"]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00006
Nick Coghlanafd5e632006-05-03 13:02:47 +00007class GeneratorContextManager(object):
8 """Helper for @contextmanager decorator."""
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00009
10 def __init__(self, gen):
11 self.gen = gen
12
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000013 def __enter__(self):
14 try:
15 return self.gen.next()
16 except StopIteration:
17 raise RuntimeError("generator didn't yield")
18
19 def __exit__(self, type, value, traceback):
20 if type is None:
21 try:
22 self.gen.next()
23 except StopIteration:
24 return
25 else:
26 raise RuntimeError("generator didn't stop")
27 else:
Nick Coghlane53fcfd2007-11-07 12:26:40 +000028 if value is None:
29 # Need to force instantiation so we can reliably
30 # tell if we get the same exception back
31 value = type()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000032 try:
33 self.gen.throw(type, value, traceback)
Phillip J. Eby6edd2582006-03-25 00:28:24 +000034 raise RuntimeError("generator didn't stop after throw()")
Phillip J. Eby93149d92006-04-10 17:56:29 +000035 except StopIteration, exc:
Phillip J. Eby93880202006-04-03 21:20:07 +000036 # Suppress the exception *unless* it's the same exception that
37 # was passed to throw(). This prevents a StopIteration
38 # raised inside the "with" statement from being suppressed
Phillip J. Eby93149d92006-04-10 17:56:29 +000039 return exc is not value
Phillip J. Eby6edd2582006-03-25 00:28:24 +000040 except:
Phillip J. Ebyccc7bb42006-03-25 04:32:12 +000041 # only re-raise if it's *not* the exception that was
42 # passed to throw(), because __exit__() must not raise
43 # an exception unless __exit__() itself failed. But throw()
44 # has to raise the exception to signal propagation, so this
45 # fixes the impedance mismatch between the throw() protocol
46 # and the __exit__() protocol.
47 #
Phillip J. Eby6edd2582006-03-25 00:28:24 +000048 if sys.exc_info()[1] is not value:
49 raise
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000050
51
Nick Coghlanafd5e632006-05-03 13:02:47 +000052def contextmanager(func):
53 """@contextmanager decorator.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000054
55 Typical usage:
56
Nick Coghlanafd5e632006-05-03 13:02:47 +000057 @contextmanager
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000058 def some_generator(<arguments>):
59 <setup>
60 try:
61 yield <value>
62 finally:
63 <cleanup>
64
65 This makes this:
66
67 with some_generator(<arguments>) as <variable>:
68 <body>
69
70 equivalent to this:
71
72 <setup>
73 try:
74 <variable> = <value>
75 <body>
76 finally:
77 <cleanup>
78
79 """
80 def helper(*args, **kwds):
Nick Coghlanafd5e632006-05-03 13:02:47 +000081 return GeneratorContextManager(func(*args, **kwds))
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000082 try:
83 helper.__name__ = func.__name__
84 helper.__doc__ = func.__doc__
Phillip J. Eby35fd1422006-03-28 00:07:24 +000085 helper.__dict__ = func.__dict__
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000086 except:
87 pass
88 return helper
89
90
Nick Coghlanafd5e632006-05-03 13:02:47 +000091@contextmanager
Guido van Rossumda5b7012006-05-02 19:47:52 +000092def nested(*managers):
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000093 """Support multiple context managers in a single with-statement.
94
95 Code like this:
96
97 with nested(A, B, C) as (X, Y, Z):
98 <body>
99
100 is equivalent to this:
101
102 with A as X:
103 with B as Y:
104 with C as Z:
105 <body>
106
107 """
108 exits = []
109 vars = []
Guido van Rossumf6694362006-03-10 02:28:35 +0000110 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000111 try:
112 try:
Guido van Rossumda5b7012006-05-02 19:47:52 +0000113 for mgr in managers:
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000114 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):
Nick Coghlanda2268f2006-04-24 04:37:15 +0000130 # Don't rely on sys.exc_info() still containing
131 # the right information. Another exception may
132 # have been raised and caught by an exit method
133 raise exc[0], exc[1], exc[2]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000134
135
Nick Coghlana7e820a2006-04-25 10:56:51 +0000136class closing(object):
137 """Context to automatically close something at the end of a block.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000138
139 Code like this:
140
141 with closing(<module>.open(<arguments>)) as f:
142 <block>
143
144 is equivalent to this:
145
146 f = <module>.open(<arguments>)
147 try:
148 <block>
149 finally:
150 f.close()
151
152 """
Nick Coghlana7e820a2006-04-25 10:56:51 +0000153 def __init__(self, thing):
154 self.thing = thing
Nick Coghlana7e820a2006-04-25 10:56:51 +0000155 def __enter__(self):
156 return self.thing
157 def __exit__(self, *exc_info):
158 self.thing.close()