mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +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 @@ def test_for_break_continue_inside_try_finally_block(self): | ||||||
|             """ |             """ | ||||||
|         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 @@ def test_for_break_inside_finally_block(self): | ||||||
|                 finally: |                 finally: | ||||||
|                     if z: |                     if z: | ||||||
|                         break |                         break | ||||||
|  |                     elif u: | ||||||
|  |                         continue | ||||||
|                     else: |                     else: | ||||||
|                         a |                         a | ||||||
|             else: |             else: | ||||||
|  |  | ||||||
|  | @ -138,15 +138,6 @@ def ckmsg(src, msg): | ||||||
|             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 @@ def test_break_in_finally(self): | ||||||
|                 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 @@ | ||||||
|     >>> 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 @@ | ||||||
|     ...            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 @@ | ||||||
|     ...                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 @@ | ||||||
|     ...         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
	
	 Serhiy Storchaka
						Serhiy Storchaka