Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For-else deserves its own section in the tutorial #123946

Merged
merged 3 commits into from
Sep 25, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 53 additions & 31 deletions Doc/tutorial/controlflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,52 @@ arguments. In chapter :ref:`tut-structures`, we will discuss in more detail abo

.. _tut-break:

:keyword:`!break` and :keyword:`!continue` Statements, and :keyword:`!else` Clauses on Loops
============================================================================================
:keyword:`!break` and :keyword:`!continue` Statements
=====================================================

The :keyword:`break` statement breaks out of the innermost enclosing
:keyword:`for` or :keyword:`while` loop.
:keyword:`for` or :keyword:`while` loop::

A :keyword:`!for` or :keyword:`!while` loop can include an :keyword:`!else` clause.
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(f"{n} equals {x} * {n//x}")
... break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3

The :keyword:`continue` statement continues with the next
iteration of the loop::

>>> for num in range(2, 10):
... if num % 2 == 0:
... print(f"Found an even number {num}")
... continue
... print(f"Found an odd number {num}")
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9

.. _tut-for-else:

:keyword:`!else` Clauses on Loops
=================================

In a :keyword:`!for` or :keyword:`!while` loop the :keyword:`!break` statement
may be paired with an :keyword:`!else` clause. If the loop finishes without
executing the break, the else clause executes.
nedbat marked this conversation as resolved.
Show resolved Hide resolved

In a :keyword:`for` loop, the :keyword:`!else` clause is executed
after the loop reaches its final iteration.
after the loop finishes its final iteration, that is, if no break occurred.

In a :keyword:`while` loop, it's executed after the loop's condition becomes false.

Expand Down Expand Up @@ -198,32 +234,18 @@ which searches for prime numbers::
9 equals 3 * 3

(Yes, this is the correct code. Look closely: the ``else`` clause belongs to
the :keyword:`for` loop, **not** the :keyword:`if` statement.)

When used with a loop, the ``else`` clause has more in common with the
``else`` clause of a :keyword:`try` statement than it does with that of
:keyword:`if` statements: a :keyword:`try` statement's ``else`` clause runs
when no exception occurs, and a loop's ``else`` clause runs when no ``break``
occurs. For more on the :keyword:`!try` statement and exceptions, see
:ref:`tut-handling`.

The :keyword:`continue` statement, also borrowed from C, continues with the next
iteration of the loop::

>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found an odd number", num)
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
the ``for`` loop, **not** the ``if`` statement.)

One way to think of the else clause is to imagine it paired with the ``if``
inside the loop. If you conceptually unroll the loop, you have an if/if/if/else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in Discourse, I think the concept of “loop unrolling” may sound too foreign to novices reading a tutorial. The paragraph may serve the purpose just as well without this sentence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

As the loop executes, it will run a sequence like if/if/if/else. The if is inside the loop, encountered a number of times. If the condition is ever true, a break will happen. If the condition is never true, the else clause outside the loop will execute.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes that sounds to be in much plainer English to me. 👍

Copy link

@AsgerJon AsgerJon Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently wrote about this elsewhere and even included an example:

class Integer:

  __fallback_value__ = 0
  __inner_value__ = None

  def __init__(self, *args) -> None:
    """This constructor method accepts any number of positional arguments.
     It then implements the under-used and underappreciated 'for-loop'
     ending with an 'else' clause. The code in the 'else' block runs 
     after the loop completes. The point is that the 'break' keyword also 
     applies to this block. So if 'break' is encountered, the code in the 
     'else' block does not run. To translate to natural language: 
     
     'Go through each positional argument and when you find an integer, 
     assign it to the inner value. Use the fallback value if you have not found any integer 
     after looking through each positional argument.'  
     
     It is the opinion of this author that the 'else' clause in a loop is
     both underused and underappreciated."""
    for arg in args:
      if isinstance(arg, int):
        self.__inner_value__ = arg
        break
    else:
      self.__inner_value__ = self.__fallback_value__

Hope this helps in some small way!

structure. The ``if`` is inside the loop, executing a ``break``, and the
``else`` is the else clause outside the loop.

When used with a loop, the ``else`` clause has more in common with the ``else``
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this paragraph necessary? Many readers of the tutorial will not be familiar with try/else, so I'm not sure this helps many people.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't expect people to read purely linearly, and this could help cement the semantics in the reader's mind.

clause of a :keyword:`try` statement than it does with that of ``if``
statements: a ``try`` statement's ``else`` clause runs when no exception
occurs, and a loop's ``else`` clause runs when no ``break`` occurs. For more on
the ``try`` statement and exceptions, see :ref:`tut-handling`.

.. _tut-pass:

Expand Down
Loading