blob: 33d83a6e14cab8f066dc4110f90dcce70c7ce478 [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)
33 except StopIteration:
34 pass
35
36
37def contextmanager(func):
38 """@contextmanager decorator.
39
40 Typical usage:
41
42 @contextmanager
43 def some_generator(<arguments>):
44 <setup>
45 try:
46 yield <value>
47 finally:
48 <cleanup>
49
50 This makes this:
51
52 with some_generator(<arguments>) as <variable>:
53 <body>
54
55 equivalent to this:
56
57 <setup>
58 try:
59 <variable> = <value>
60 <body>
61 finally:
62 <cleanup>
63
64 """
65 def helper(*args, **kwds):
66 return GeneratorContextManager(func(*args, **kwds))
67 try:
68 helper.__name__ = func.__name__
69 helper.__doc__ = func.__doc__
70 except:
71 pass
72 return helper
73
74
75@contextmanager
76def nested(*contexts):
77 """Support multiple context managers in a single with-statement.
78
79 Code like this:
80
81 with nested(A, B, C) as (X, Y, Z):
82 <body>
83
84 is equivalent to this:
85
86 with A as X:
87 with B as Y:
88 with C as Z:
89 <body>
90
91 """
92 exits = []
93 vars = []
94 exc = (None, None, None)
95 try:
96 try:
97 for context in contexts:
98 mgr = context.__context__()
99 exit = mgr.__exit__
100 enter = mgr.__enter__
101 vars.append(enter())
102 exits.append(exit)
103 yield vars
104 except:
105 exc = sys.exc_info()
106 finally:
107 while exits:
108 exit = exits.pop()
109 try:
110 exit(*exc)
111 except:
112 exc = sys.exc_info()
113 if exc != (None, None, None):
114 raise
115
116
117@contextmanager
118def closing(thing):
119 """Context manager to automatically close something at the end of a block.
120
121 Code like this:
122
123 with closing(<module>.open(<arguments>)) as f:
124 <block>
125
126 is equivalent to this:
127
128 f = <module>.open(<arguments>)
129 try:
130 <block>
131 finally:
132 f.close()
133
134 """
135 try:
136 yield thing
137 finally:
138 thing.close()