mirror of
https://github.com/python/cpython.git
synced 2025-09-18 22:50:26 +00:00
bpo-32489: Allow 'continue' in 'finally' clause. (GH-5822)
This commit is contained in:
parent
134cb01cda
commit
fe2bbb1869
10 changed files with 97 additions and 70 deletions
|
@ -698,8 +698,8 @@ iterations of the loop.
|
||||||
removed from the block stack.
|
removed from the block stack.
|
||||||
|
|
||||||
It is similar to :opcode:`END_FINALLY`, but doesn't change the bytecode
|
It is similar to :opcode:`END_FINALLY`, but doesn't change the bytecode
|
||||||
counter nor raise an exception. Used for implementing :keyword:`break`
|
counter nor raise an exception. Used for implementing :keyword:`break`,
|
||||||
and :keyword:`return` in the :keyword:`finally` block.
|
:keyword:`continue` and :keyword:`return` in the :keyword:`finally` block.
|
||||||
|
|
||||||
.. versionadded:: 3.8
|
.. versionadded:: 3.8
|
||||||
|
|
||||||
|
|
|
@ -321,8 +321,8 @@ not handled, the exception is temporarily saved. The :keyword:`finally` clause
|
||||||
is executed. If there is a saved exception it is re-raised at the end of the
|
is executed. If there is a saved exception it is re-raised at the end of the
|
||||||
:keyword:`finally` clause. If the :keyword:`finally` clause raises another
|
:keyword:`finally` clause. If the :keyword:`finally` clause raises another
|
||||||
exception, the saved exception is set as the context of the new exception.
|
exception, the saved exception is set as the context of the new exception.
|
||||||
If the :keyword:`finally` clause executes a :keyword:`return` or :keyword:`break`
|
If the :keyword:`finally` clause executes a :keyword:`return`, :keyword:`break`
|
||||||
statement, the saved exception is discarded::
|
or :keyword:`continue` statement, the saved exception is discarded::
|
||||||
|
|
||||||
>>> def f():
|
>>> def f():
|
||||||
... try:
|
... try:
|
||||||
|
@ -343,10 +343,7 @@ the :keyword:`finally` clause.
|
||||||
|
|
||||||
When a :keyword:`return`, :keyword:`break` or :keyword:`continue` statement is
|
When a :keyword:`return`, :keyword:`break` or :keyword:`continue` statement is
|
||||||
executed in the :keyword:`try` suite of a :keyword:`try`...\ :keyword:`finally`
|
executed in the :keyword:`try` suite of a :keyword:`try`...\ :keyword:`finally`
|
||||||
statement, the :keyword:`finally` clause is also executed 'on the way out.' A
|
statement, the :keyword:`finally` clause is also executed 'on the way out.'
|
||||||
:keyword:`continue` statement is illegal in the :keyword:`finally` clause. (The
|
|
||||||
reason is a problem with the current implementation --- this restriction may be
|
|
||||||
lifted in the future).
|
|
||||||
|
|
||||||
The return value of a function is determined by the last :keyword:`return`
|
The return value of a function is determined by the last :keyword:`return`
|
||||||
statement executed. Since the :keyword:`finally` clause always executes, a
|
statement executed. Since the :keyword:`finally` clause always executes, a
|
||||||
|
@ -366,6 +363,10 @@ Additional information on exceptions can be found in section :ref:`exceptions`,
|
||||||
and information on using the :keyword:`raise` statement to generate exceptions
|
and information on using the :keyword:`raise` statement to generate exceptions
|
||||||
may be found in section :ref:`raise`.
|
may be found in section :ref:`raise`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
Prior to Python 3.8, a :keyword:`continue` statement was illegal in the
|
||||||
|
:keyword:`finally` clause due to a problem with the implementation.
|
||||||
|
|
||||||
|
|
||||||
.. _with:
|
.. _with:
|
||||||
.. _as:
|
.. _as:
|
||||||
|
|
|
@ -686,9 +686,8 @@ The :keyword:`continue` statement
|
||||||
continue_stmt: "continue"
|
continue_stmt: "continue"
|
||||||
|
|
||||||
:keyword:`continue` may only occur syntactically nested in a :keyword:`for` or
|
:keyword:`continue` may only occur syntactically nested in a :keyword:`for` or
|
||||||
:keyword:`while` loop, but not nested in a function or class definition or
|
:keyword:`while` loop, but not nested in a function or class definition within
|
||||||
:keyword:`finally` clause within that loop. It continues with the next
|
that loop. It continues with the next cycle of the nearest enclosing loop.
|
||||||
cycle of the nearest enclosing loop.
|
|
||||||
|
|
||||||
When :keyword:`continue` passes control out of a :keyword:`try` statement with a
|
When :keyword:`continue` passes control out of a :keyword:`try` statement with a
|
||||||
:keyword:`finally` clause, that :keyword:`finally` clause is executed before
|
:keyword:`finally` clause, that :keyword:`finally` clause is executed before
|
||||||
|
|
|
@ -72,6 +72,11 @@ New Features
|
||||||
Other Language Changes
|
Other Language Changes
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
* A :keyword:`continue` statement was illegal in the :keyword:`finally` clause
|
||||||
|
due to a problem with the implementation. In Python 3.8 this restriction
|
||||||
|
was lifted.
|
||||||
|
(Contributed by Serhiy Storchaka in :issue:`32489`.)
|
||||||
|
|
||||||
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
|
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
|
||||||
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)
|
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)
|
||||||
|
|
||||||
|
|
|
@ -856,7 +856,7 @@ class TestStackSizeStability(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
self.check_stack_size(snippet)
|
self.check_stack_size(snippet)
|
||||||
|
|
||||||
def test_for_break_inside_finally_block(self):
|
def test_for_break_continue_inside_finally_block(self):
|
||||||
snippet = """
|
snippet = """
|
||||||
for x in y:
|
for x in y:
|
||||||
try:
|
try:
|
||||||
|
@ -864,6 +864,8 @@ class TestStackSizeStability(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
if z:
|
if z:
|
||||||
break
|
break
|
||||||
|
elif u:
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
a
|
a
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -138,15 +138,6 @@ class ExceptionTests(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail("failed to get expected SyntaxError")
|
self.fail("failed to get expected SyntaxError")
|
||||||
|
|
||||||
s = '''while 1:
|
|
||||||
try:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
continue'''
|
|
||||||
|
|
||||||
if not sys.platform.startswith('java'):
|
|
||||||
ckmsg(s, "'continue' not supported inside 'finally' clause")
|
|
||||||
|
|
||||||
s = '''if 1:
|
s = '''if 1:
|
||||||
try:
|
try:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -859,6 +859,59 @@ class GrammarTests(unittest.TestCase):
|
||||||
break
|
break
|
||||||
self.assertEqual(count, 0)
|
self.assertEqual(count, 0)
|
||||||
|
|
||||||
|
def test_continue_in_finally(self):
|
||||||
|
count = 0
|
||||||
|
while count < 2:
|
||||||
|
count += 1
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
self.assertEqual(count, 2)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
while count < 2:
|
||||||
|
count += 1
|
||||||
|
try:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
self.assertEqual(count, 2)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
while count < 2:
|
||||||
|
count += 1
|
||||||
|
try:
|
||||||
|
1/0
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
self.assertEqual(count, 2)
|
||||||
|
|
||||||
|
for count in [0, 1]:
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
for count in [0, 1]:
|
||||||
|
try:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
for count in [0, 1]:
|
||||||
|
try:
|
||||||
|
1/0
|
||||||
|
finally:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
def test_return_in_finally(self):
|
def test_return_in_finally(self):
|
||||||
def g1():
|
def g1():
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -298,7 +298,7 @@ continue in for loop under finally should be ok.
|
||||||
>>> test()
|
>>> test()
|
||||||
9
|
9
|
||||||
|
|
||||||
Start simple, a continue in a finally should not be allowed.
|
continue in a finally should be ok.
|
||||||
|
|
||||||
>>> def test():
|
>>> def test():
|
||||||
... for abc in range(10):
|
... for abc in range(10):
|
||||||
|
@ -306,11 +306,9 @@ Start simple, a continue in a finally should not be allowed.
|
||||||
... pass
|
... pass
|
||||||
... finally:
|
... finally:
|
||||||
... continue
|
... continue
|
||||||
Traceback (most recent call last):
|
... print(abc)
|
||||||
...
|
>>> test()
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
9
|
||||||
|
|
||||||
This is essentially a continue in a finally which should not be allowed.
|
|
||||||
|
|
||||||
>>> def test():
|
>>> def test():
|
||||||
... for abc in range(10):
|
... for abc in range(10):
|
||||||
|
@ -321,9 +319,24 @@ This is essentially a continue in a finally which should not be allowed.
|
||||||
... continue
|
... continue
|
||||||
... except:
|
... except:
|
||||||
... pass
|
... pass
|
||||||
Traceback (most recent call last):
|
... print(abc)
|
||||||
...
|
>>> test()
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
9
|
||||||
|
|
||||||
|
>>> def test():
|
||||||
|
... for abc in range(10):
|
||||||
|
... try:
|
||||||
|
... pass
|
||||||
|
... finally:
|
||||||
|
... try:
|
||||||
|
... pass
|
||||||
|
... except:
|
||||||
|
... continue
|
||||||
|
... print(abc)
|
||||||
|
>>> test()
|
||||||
|
9
|
||||||
|
|
||||||
|
A continue outside loop should not be allowed.
|
||||||
|
|
||||||
>>> def foo():
|
>>> def foo():
|
||||||
... try:
|
... try:
|
||||||
|
@ -332,42 +345,7 @@ This is essentially a continue in a finally which should not be allowed.
|
||||||
... continue
|
... continue
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
SyntaxError: 'continue' not properly in loop
|
||||||
|
|
||||||
>>> def foo():
|
|
||||||
... for a in ():
|
|
||||||
... try:
|
|
||||||
... pass
|
|
||||||
... finally:
|
|
||||||
... continue
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
|
||||||
|
|
||||||
>>> def foo():
|
|
||||||
... for a in ():
|
|
||||||
... try:
|
|
||||||
... pass
|
|
||||||
... finally:
|
|
||||||
... try:
|
|
||||||
... continue
|
|
||||||
... finally:
|
|
||||||
... pass
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
|
||||||
|
|
||||||
>>> def foo():
|
|
||||||
... for a in ():
|
|
||||||
... try: pass
|
|
||||||
... finally:
|
|
||||||
... try:
|
|
||||||
... pass
|
|
||||||
... except:
|
|
||||||
... continue
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
SyntaxError: 'continue' not supported inside 'finally' clause
|
|
||||||
|
|
||||||
There is one test for a break that is not in a loop. The compiler
|
There is one test for a break that is not in a loop. The compiler
|
||||||
uses a single data structure to keep track of try-finally and loops,
|
uses a single data structure to keep track of try-finally and loops,
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
A :keyword:`continue` statement is now allowed in the :keyword:`finally`
|
||||||
|
clause.
|
|
@ -2625,10 +2625,6 @@ compiler_continue(struct compiler *c)
|
||||||
ADDOP_JABS(c, JUMP_ABSOLUTE, info->fb_block);
|
ADDOP_JABS(c, JUMP_ABSOLUTE, info->fb_block);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (info->fb_type == FINALLY_END) {
|
|
||||||
return compiler_error(c,
|
|
||||||
"'continue' not supported inside 'finally' clause");
|
|
||||||
}
|
|
||||||
if (!compiler_unwind_fblock(c, info, 0))
|
if (!compiler_unwind_fblock(c, info, 0))
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue