blob: 6fcb5360eb2bdbfa8b7cb2840dc7fb49bbba66dd [file] [log] [blame]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00001"""Utilities for with-statement contexts. See PEP 343."""
2
3import sys
Georg Brandl655fc702008-04-30 21:08:42 +00004from functools import wraps
Raymond Hettinger822b87f2009-05-29 01:46:48 +00005from warnings import warn
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00006
Nick Coghlanafd5e632006-05-03 13:02:47 +00007__all__ = ["contextmanager", "nested", "closing"]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +00008
Nick Coghlanafd5e632006-05-03 13:02:47 +00009class GeneratorContextManager(object):
10 """Helper for @contextmanager decorator."""
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000011
12 def __init__(self, gen):
13 self.gen = gen
14
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000015 def __enter__(self):
16 try:
17 return self.gen.next()
18 except StopIteration:
19 raise RuntimeError("generator didn't yield")
20
21 def __exit__(self, type, value, traceback):
22 if type is None:
23 try:
24 self.gen.next()
25 except StopIteration:
26 return
27 else:
28 raise RuntimeError("generator didn't stop")
29 else:
Nick Coghlan3814a912007-11-02 10:09:12 +000030 if value is None:
31 # Need to force instantiation so we can reliably
32 # tell if we get the same exception back
33 value = type()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000034 try:
35 self.gen.throw(type, value, traceback)
Phillip J. Eby6edd2582006-03-25 00:28:24 +000036 raise RuntimeError("generator didn't stop after throw()")
Phillip J. Eby93149d92006-04-10 17:56:29 +000037 except StopIteration, exc:
Phillip J. Eby93880202006-04-03 21:20:07 +000038 # Suppress the exception *unless* it's the same exception that
39 # was passed to throw(). This prevents a StopIteration
40 # raised inside the "with" statement from being suppressed
Phillip J. Eby93149d92006-04-10 17:56:29 +000041 return exc is not value
Phillip J. Eby6edd2582006-03-25 00:28:24 +000042 except:
Phillip J. Ebyccc7bb42006-03-25 04:32:12 +000043 # only re-raise if it's *not* the exception that was
44 # passed to throw(), because __exit__() must not raise
45 # an exception unless __exit__() itself failed. But throw()
46 # has to raise the exception to signal propagation, so this
47 # fixes the impedance mismatch between the throw() protocol
48 # and the __exit__() protocol.
49 #
Phillip J. Eby6edd2582006-03-25 00:28:24 +000050 if sys.exc_info()[1] is not value:
51 raise
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000052
53
Nick Coghlanafd5e632006-05-03 13:02:47 +000054def contextmanager(func):
55 """@contextmanager decorator.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000056
57 Typical usage:
58
Nick Coghlanafd5e632006-05-03 13:02:47 +000059 @contextmanager
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000060 def some_generator(<arguments>):
61 <setup>
62 try:
63 yield <value>
64 finally:
65 <cleanup>
66
67 This makes this:
68
69 with some_generator(<arguments>) as <variable>:
70 <body>
71
72 equivalent to this:
73
74 <setup>
75 try:
76 <variable> = <value>
77 <body>
78 finally:
79 <cleanup>
80
81 """
Georg Brandl655fc702008-04-30 21:08:42 +000082 @wraps(func)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000083 def helper(*args, **kwds):
Nick Coghlanafd5e632006-05-03 13:02:47 +000084 return GeneratorContextManager(func(*args, **kwds))
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000085 return helper
86
87
Nick Coghlanafd5e632006-05-03 13:02:47 +000088@contextmanager
Guido van Rossumda5b7012006-05-02 19:47:52 +000089def nested(*managers):
Guido van Rossum1a5e21e2006-02-28 21:57:43 +000090 """Support multiple context managers in a single with-statement.
91
92 Code like this:
93
94 with nested(A, B, C) as (X, Y, Z):
95 <body>
96
97 is equivalent to this:
98
99 with A as X:
100 with B as Y:
101 with C as Z:
102 <body>
103
104 """
Raymond Hettinger822b87f2009-05-29 01:46:48 +0000105 warn("With-statements now directly support multiple context managers",
Raymond Hettinger1917ad52009-06-10 16:15:02 +0000106 DeprecationWarning, 3)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000107 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:
Georg Brandlac4018a2007-08-23 18:11:33 +0000111 for mgr in managers:
112 exit = mgr.__exit__
113 enter = mgr.__enter__
114 vars.append(enter())
115 exits.append(exit)
116 yield vars
117 except:
118 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000119 finally:
120 while exits:
121 exit = exits.pop()
122 try:
Guido van Rossumf6694362006-03-10 02:28:35 +0000123 if exit(*exc):
124 exc = (None, None, None)
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000125 except:
126 exc = sys.exc_info()
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000127 if exc != (None, None, None):
Nick Coghlanda2268f2006-04-24 04:37:15 +0000128 # Don't rely on sys.exc_info() still containing
129 # the right information. Another exception may
130 # have been raised and caught by an exit method
131 raise exc[0], exc[1], exc[2]
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000132
133
Nick Coghlana7e820a2006-04-25 10:56:51 +0000134class closing(object):
135 """Context to automatically close something at the end of a block.
Guido van Rossum1a5e21e2006-02-28 21:57:43 +0000136
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 """
Nick Coghlana7e820a2006-04-25 10:56:51 +0000151 def __init__(self, thing):
152 self.thing = thing
Nick Coghlana7e820a2006-04-25 10:56:51 +0000153 def __enter__(self):
154 return self.thing
155 def __exit__(self, *exc_info):
156 self.thing.close()