Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1ef1795

Browse files
author
Stefan Krah
committed
Merge.
2 parents 5ddbcfc + a5bd2a1 commit 1ef1795

3 files changed

Lines changed: 28 additions & 27 deletions

File tree

Lib/contextlib.py

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -225,32 +225,21 @@ def __enter__(self):
225225
return self
226226

227227
def __exit__(self, *exc_details):
228-
if not self._exit_callbacks:
229-
return
230-
# This looks complicated, but it is really just
231-
# setting up a chain of try-expect statements to ensure
232-
# that outer callbacks still get invoked even if an
233-
# inner one throws an exception
234-
def _invoke_next_callback(exc_details):
235-
# Callbacks are removed from the list in FIFO order
236-
# but the recursion means they're invoked in LIFO order
237-
cb = self._exit_callbacks.popleft()
238-
if not self._exit_callbacks:
239-
# Innermost callback is invoked directly
240-
return cb(*exc_details)
241-
# More callbacks left, so descend another level in the stack
228+
# Callbacks are invoked in LIFO order to match the behaviour of
229+
# nested context managers
230+
suppressed_exc = False
231+
while self._exit_callbacks:
232+
cb = self._exit_callbacks.pop()
242233
try:
243-
suppress_exc = _invoke_next_callback(exc_details)
234+
if cb(*exc_details):
235+
suppressed_exc = True
236+
exc_details = (None, None, None)
244237
except:
245-
suppress_exc = cb(*sys.exc_info())
246-
# Check if this cb suppressed the inner exception
247-
if not suppress_exc:
238+
new_exc_details = sys.exc_info()
239+
if exc_details != (None, None, None):
240+
# simulate the stack of exceptions by setting the context
241+
new_exc_details[1].__context__ = exc_details[1]
242+
if not self._exit_callbacks:
248243
raise
249-
else:
250-
# Check if inner cb suppressed the original exception
251-
if suppress_exc:
252-
exc_details = (None, None, None)
253-
suppress_exc = cb(*exc_details) or suppress_exc
254-
return suppress_exc
255-
# Kick off the recursive chain
256-
return _invoke_next_callback(exc_details)
244+
exc_details = new_exc_details
245+
return suppressed_exc

Lib/test/test_contextlib.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,12 @@ def test_exit_exception_chaining_suppress(self):
572572
stack.push(lambda *exc: 1/0)
573573
stack.push(lambda *exc: {}[1])
574574

575+
def test_excessive_nesting(self):
576+
# The original implementation would die with RecursionError here
577+
with ExitStack() as stack:
578+
for i in range(10000):
579+
stack.callback(int)
580+
575581
def test_instance_bypass(self):
576582
class Example(object): pass
577583
cm = Example()

Misc/NEWS

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ What's New in Python 3.3.0 Beta 1?
77

88
*Release date: TBD*
99

10+
Library
11+
-------
12+
13+
- Issue #14963: Convert contextlib.ExitStack.__exit__ to use an iterative
14+
algorithm (Patch by Alon Horev)
15+
1016
Tests
1117
-----
1218

1319
- Issue #14963 (partial): Add test cases for exception handling behaviour
14-
in contextlib.ContextStack (Initial patch by Alon Horev)
20+
in contextlib.ExitStack (Initial patch by Alon Horev)
1521

1622
What's New in Python 3.3.0 Alpha 4?
1723
===================================

0 commit comments

Comments
 (0)