@@ -113,18 +113,20 @@ def __init__(self, func, args, kwds):
113
113
# for the class instead.
114
114
# See http://bugs.python.org/issue19404 for more details.
115
115
116
-
117
- class _GeneratorContextManager (_GeneratorContextManagerBase ,
118
- AbstractContextManager ,
119
- ContextDecorator ):
120
- """Helper for @contextmanager decorator."""
121
-
122
116
def _recreate_cm (self ):
123
- # _GCM instances are one-shot context managers, so the
117
+ # _GCMB instances are one-shot context managers, so the
124
118
# CM must be recreated each time a decorated function is
125
119
# called
126
120
return self .__class__ (self .func , self .args , self .kwds )
127
121
122
+
123
+ class _GeneratorContextManager (
124
+ _GeneratorContextManagerBase ,
125
+ AbstractContextManager ,
126
+ ContextDecorator ,
127
+ ):
128
+ """Helper for @contextmanager decorator."""
129
+
128
130
def __enter__ (self ):
129
131
# do not keep args and kwds alive unnecessarily
130
132
# they are only needed for recreation, which is not possible anymore
@@ -134,8 +136,8 @@ def __enter__(self):
134
136
except StopIteration :
135
137
raise RuntimeError ("generator didn't yield" ) from None
136
138
137
- def __exit__ (self , type , value , traceback ):
138
- if type is None :
139
+ def __exit__ (self , typ , value , traceback ):
140
+ if typ is None :
139
141
try :
140
142
next (self .gen )
141
143
except StopIteration :
@@ -146,9 +148,9 @@ def __exit__(self, type, value, traceback):
146
148
if value is None :
147
149
# Need to force instantiation so we can reliably
148
150
# tell if we get the same exception back
149
- value = type ()
151
+ value = typ ()
150
152
try :
151
- self .gen .throw (type , value , traceback )
153
+ self .gen .throw (typ , value , traceback )
152
154
except StopIteration as exc :
153
155
# Suppress StopIteration *unless* it's the same exception that
154
156
# was passed to throw(). This prevents a StopIteration
@@ -158,81 +160,93 @@ def __exit__(self, type, value, traceback):
158
160
# Don't re-raise the passed in exception. (issue27122)
159
161
if exc is value :
160
162
return False
161
- # Likewise, avoid suppressing if a StopIteration exception
163
+ # Avoid suppressing if a StopIteration exception
162
164
# was passed to throw() and later wrapped into a RuntimeError
163
- # (see PEP 479).
164
- if type is StopIteration and exc .__cause__ is value :
165
+ # (see PEP 479 for sync generators; async generators also
166
+ # have this behavior). But do this only if the exception wrapped
167
+ # by the RuntimeError is actually Stop(Async)Iteration (see
168
+ # issue29692).
169
+ if (
170
+ isinstance (value , StopIteration )
171
+ and exc .__cause__ is value
172
+ ):
165
173
return False
166
174
raise
167
- except :
175
+ except BaseException as exc :
168
176
# only re-raise if it's *not* the exception that was
169
177
# passed to throw(), because __exit__() must not raise
170
178
# an exception unless __exit__() itself failed. But throw()
171
179
# has to raise the exception to signal propagation, so this
172
180
# fixes the impedance mismatch between the throw() protocol
173
181
# and the __exit__() protocol.
174
- #
175
- # This cannot use 'except BaseException as exc' (as in the
176
- # async implementation) to maintain compatibility with
177
- # Python 2, where old-style class exceptions are not caught
178
- # by 'except BaseException'.
179
- if sys .exc_info ()[1 ] is value :
180
- return False
181
- raise
182
+ if exc is not value :
183
+ raise
184
+ return False
182
185
raise RuntimeError ("generator didn't stop after throw()" )
183
186
184
-
185
- class _AsyncGeneratorContextManager (_GeneratorContextManagerBase ,
186
- AbstractAsyncContextManager ,
187
- AsyncContextDecorator ):
188
- """Helper for @asynccontextmanager."""
189
-
190
- def _recreate_cm (self ):
191
- # _AGCM instances are one-shot context managers, so the
192
- # ACM must be recreated each time a decorated function is
193
- # called
194
- return self .__class__ (self .func , self .args , self .kwds )
187
+ class _AsyncGeneratorContextManager (
188
+ _GeneratorContextManagerBase ,
189
+ AbstractAsyncContextManager ,
190
+ AsyncContextDecorator ,
191
+ ):
192
+ """Helper for @asynccontextmanager decorator."""
195
193
196
194
async def __aenter__ (self ):
195
+ # do not keep args and kwds alive unnecessarily
196
+ # they are only needed for recreation, which is not possible anymore
197
+ del self .args , self .kwds , self .func
197
198
try :
198
- return await self .gen . __anext__ ( )
199
+ return await anext ( self .gen )
199
200
except StopAsyncIteration :
200
201
raise RuntimeError ("generator didn't yield" ) from None
201
202
202
203
async def __aexit__ (self , typ , value , traceback ):
203
204
if typ is None :
204
205
try :
205
- await self .gen . __anext__ ( )
206
+ await anext ( self .gen )
206
207
except StopAsyncIteration :
207
- return
208
+ return False
208
209
else :
209
210
raise RuntimeError ("generator didn't stop" )
210
211
else :
211
212
if value is None :
213
+ # Need to force instantiation so we can reliably
214
+ # tell if we get the same exception back
212
215
value = typ ()
213
- # See _GeneratorContextManager.__exit__ for comments on subtleties
214
- # in this implementation
215
216
try :
216
217
await self .gen .athrow (typ , value , traceback )
217
- raise RuntimeError ("generator didn't stop after athrow()" )
218
218
except StopAsyncIteration as exc :
219
+ # Suppress StopIteration *unless* it's the same exception that
220
+ # was passed to throw(). This prevents a StopIteration
221
+ # raised inside the "with" statement from being suppressed.
219
222
return exc is not value
220
223
except RuntimeError as exc :
224
+ # Don't re-raise the passed in exception. (issue27122)
221
225
if exc is value :
222
226
return False
223
- # Avoid suppressing if a StopIteration exception
224
- # was passed to throw () and later wrapped into a RuntimeError
227
+ # Avoid suppressing if a Stop(Async)Iteration exception
228
+ # was passed to athrow () and later wrapped into a RuntimeError
225
229
# (see PEP 479 for sync generators; async generators also
226
230
# have this behavior). But do this only if the exception wrapped
227
231
# by the RuntimeError is actully Stop(Async)Iteration (see
228
232
# issue29692).
229
- if isinstance (value , (StopIteration , StopAsyncIteration )):
230
- if exc .__cause__ is value :
231
- return False
233
+ if (
234
+ isinstance (value , (StopIteration , StopAsyncIteration ))
235
+ and exc .__cause__ is value
236
+ ):
237
+ return False
232
238
raise
233
239
except BaseException as exc :
240
+ # only re-raise if it's *not* the exception that was
241
+ # passed to throw(), because __exit__() must not raise
242
+ # an exception unless __exit__() itself failed. But throw()
243
+ # has to raise the exception to signal propagation, so this
244
+ # fixes the impedance mismatch between the throw() protocol
245
+ # and the __exit__() protocol.
234
246
if exc is not value :
235
247
raise
248
+ return False
249
+ raise RuntimeError ("generator didn't stop after athrow()" )
236
250
237
251
238
252
def contextmanager (func ):
0 commit comments