Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita6f948b

Browse files
committed
Fix exception context in @contextmanagers
Previously, when using a `try...yield...except` construct within a`@contextmanager` function, an exception raised by the `yield` wouldn'tbe properly cleared when handled the `except` block. Instead, calling`sys.exception()` would continue to return the exception, leading toconfusing tracebacks and logs.This was happening due to the way that the `@contextmanager` decoratordrives its decorated generator function. When an exception occurs, ituses `.throw(exc)` to throw the exception into the generator. However,even if the generator handles this exception, because the exception wasthrown into it in the context of the `@contextmanager` decoratorhandling it (and not being finished yet), `sys.exception()` was notbeing reset.In order to fix this, the exception context as the `@contextmanager`decorator is `__enter__`'d is stored and set as the current exceptioncontext just before throwing the new exception into the generator.Doing this means that after the generator handles the thrown exception,`sys.exception()` reverts back to what it was when the `@contextmanager`function was started.
1 parentd2f6251 commita6f948b

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

‎Lib/contextlib.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class _GeneratorContextManagerBase:
106106
"""Shared functionality for @contextmanager and @asynccontextmanager."""
107107

108108
def__init__(self,func,args,kwds):
109+
self.exc_context=None
109110
self.gen=func(*args,**kwds)
110111
self.func,self.args,self.kwds=func,args,kwds
111112
# Issue 19330: ensure context manager instances have good docstrings
@@ -134,6 +135,8 @@ class _GeneratorContextManager(
134135
"""Helper for @contextmanager decorator."""
135136

136137
def__enter__(self):
138+
# store the exception context on enter so it can be restored on exit
139+
self.exc_context=sys.exception()
137140
# do not keep args and kwds alive unnecessarily
138141
# they are only needed for recreation, which is not possible anymore
139142
delself.args,self.kwds,self.func
@@ -143,6 +146,9 @@ def __enter__(self):
143146
raiseRuntimeError("generator didn't yield")fromNone
144147

145148
def__exit__(self,typ,value,traceback):
149+
# don't keep the stored exception alive unnecessarily
150+
exc_context=self.exc_context
151+
self.exc_context=None
146152
iftypisNone:
147153
try:
148154
next(self.gen)
@@ -159,6 +165,12 @@ def __exit__(self, typ, value, traceback):
159165
# tell if we get the same exception back
160166
value=typ()
161167
try:
168+
# If the generator handles the exception thrown into it, the
169+
# exception context will revert to the actual current exception
170+
# context here. In order to make the context manager behave
171+
# like a normal function we set the current exception context
172+
# to what it was during the context manager's __enter__
173+
sys._set_exception(exc_context)
162174
self.gen.throw(value)
163175
exceptStopIterationasexc:
164176
# Suppress StopIteration *unless* it's the same exception that

‎Lib/test/test_contextlib.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,88 @@ def woohoo():
306306
withwoohoo():
307307
raiseStopIteration
308308

309+
deftest_contextmanager_handling_exception_resets_exc_info(self):
310+
# Test that sys.exc_info() is correctly unset after handling the error
311+
# when used within a context manager
312+
313+
@contextmanager
314+
defctx(reraise=False):
315+
try:
316+
self.assertIsNone(sys.exception())
317+
yield
318+
except:
319+
self.assertIsInstance(sys.exception(),ZeroDivisionError)
320+
ifreraise:
321+
raise
322+
else:
323+
self.assertIsNone(sys.exception())
324+
self.assertIsNone(sys.exception())
325+
326+
withctx():
327+
pass
328+
329+
withctx():
330+
1/0
331+
332+
withself.assertRaises(ZeroDivisionError):
333+
withctx(reraise=True):
334+
1/0
335+
336+
deftest_contextmanager_while_handling(self):
337+
# test that any exceptions currently being handled are preserved
338+
# through the context manager
339+
340+
@contextmanager
341+
defctx(reraise=False):
342+
# called while handling an IndexError --> TypeError
343+
self.assertIsInstance(sys.exception(),TypeError)
344+
self.assertIsInstance(sys.exception().__context__,IndexError)
345+
exc_ctx=sys.exception()
346+
try:
347+
# raises a ValueError --> ZeroDivisionError
348+
yield
349+
except:
350+
self.assertIsInstance(sys.exception(),ZeroDivisionError)
351+
self.assertIsInstance(sys.exception().__context__,ValueError)
352+
# original error context is preserved
353+
self.assertIs(sys.exception().__context__.__context__,exc_ctx)
354+
ifreraise:
355+
raise
356+
357+
# inner error handled, context should now be the original context
358+
self.assertIs(sys.exception(),exc_ctx)
359+
360+
try:
361+
raiseIndexError()
362+
except:
363+
try:
364+
raiseTypeError()
365+
except:
366+
withctx():
367+
try:
368+
raiseValueError()
369+
except:
370+
self.assertIsInstance(sys.exception(),ValueError)
371+
1/0
372+
self.assertIsInstance(sys.exception(),TypeError)
373+
self.assertIsInstance(sys.exception(),IndexError)
374+
375+
try:
376+
raiseIndexError()
377+
except:
378+
try:
379+
raiseTypeError()
380+
except:
381+
withself.assertRaises(ZeroDivisionError):
382+
withctx(reraise=True):
383+
try:
384+
raiseValueError()
385+
except:
386+
self.assertIsInstance(sys.exception(),ValueError)
387+
1/0
388+
self.assertIsInstance(sys.exception(),TypeError)
389+
self.assertIsInstance(sys.exception(),IndexError)
390+
309391
def_create_contextmanager_attribs(self):
310392
defattribs(**kw):
311393
defdecorate(func):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix handling of ``sys.exception()`` within ``@contextlib.contextmanager``
2+
functions. Patch by Carey Metcalfe.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp