Why this matters
Many programs do not know in advance how many values they will receive. A user types scores one after another and then types a stop word. A file has lines until it ends. A calculator keeps taking numbers until the user says they are finished. A loop that runs a fixed number of times, such as for i in range(5):, cannot handle these cases.
A sentinel loop solves this. A sentinel is a special value that means "stop". It is not data to be processed. In the examples below the sentinel is the number 0, the number -1, or the word quit. When the loop reads the sentinel, it ends.
This lesson covers loops that stop on a sentinel and the first read that every such loop needs. It does not repeat how while loops work, which is covered earlier in this module, and it does not cover looping over a list, which is the topic of the previous lesson.
The examples read from the keyboard with input(). That function waits for the user, reads one line, and returns it as text. A program that is run with a pipe or a file gets its lines from there instead. The examples that read input are not marked as runnable on this page, because they need you to type values. Save each one in a file, run it with python3, and type the values shown, one per line.
The idea: read a value, check it, repeat
A sentinel loop has three steps. It reads a value, checks whether that value is the stop value, and if not, it handles the value and reads the next one. The check happens in the while condition. The read happens before the loop and again at the end of the loop body.
Here is a program that adds up numbers until the user types 0:
total = 0
x = int(input())
while x != 0:
total += x
x = int(input())
print(f"sum = {total}")Type 5, 10, -3 and 0, each on its own line. The output is:
sum = 12Trace the run. The first input() waits and returns the text "5". The call int() turns that text into the number 5. The condition x != 0 is true, so the body adds 5 to total and reads 10. Then it adds 10 and reads -3, then adds -3 and reads 0. The condition is now false, so the loop stops. The 0 was never added, so total holds 5 + 10 - 3 = 12.
Two rules come from this program. The loop body must end by reading the next value, or x never changes and the loop runs forever. And the sentinel is checked before it is added, so it never becomes part of the total.
Note that input() always returns text. The int() call is needed to turn the text into a number. If the text is not a number, int() stops the program. Worked example 4 shows how to handle that.
The first read comes before the loop
The loop condition needs a value to check. On the first pass the loop has not read anything yet, so the program must read the first value before the while starts. This is called the priming read. It sets the loop up with a value, the way priming a pump fills it before use.
The pattern has the same shape every time:
x = int(input()) # priming read, before the loop
while x != SENTINEL:
handle(x)
x = int(input()) # next read, at the end of the bodyEach value is read exactly once. The first read happens before the loop, and each later read happens at the bottom of the body. If you forget the first read, x does not exist when the while line runs, and Python stops with a NameError. If you read the value only inside the body, the loop handles the sentinel as if it were data.
The next example shows the second problem.
Worked example 1: the sentinel is not data
The stop value must not be counted. The program below counts how many numbers were given, not counting the 0 at the end. It is correct because it checks the value before it updates the count.
count = 0
total = 0
x = int(input())
while x != 0:
count += 1
total += x
x = int(input())
print(f"count={count} total={total}")Type 4, 6 and 0, each on its own line. The output is count=2 total=10.
Now look at a version that updates the count before it checks the value. It reads the number inside the body, and it adds one to count before the check. This version is wrong.
count = 0
total = 0
while True:
y = int(input())
count += 1
if y == 0:
break
total += y
print(f"count={count} total={total}")With the same input, 4, 6 and 0, the output is count=3 total=10. The count is one too high, because the 0 was counted as a number. The total is right, because adding 0 does not change a total. A sentinel of 0 can hide this error in a sum, but it shows up in a count or an average. Check the sentinel first, and then count.
The while True: form with a break also works. It is a common way to write a sentinel loop when the check is easier to place in the middle of the body. Keep the check right after the read, before any other work on the value.
Worked example 2: an average and a zero count
An average needs a count, and a count can be zero. Dividing by zero stops the program. The program below computes the average of the numbers given before the sentinel.
total = 0
count = 0
x = int(input())
while x != 0:
total += x
count += 1
x = int(input())
print(f"count = {count}")
print(f"average = {total / count}")
print(f"integer part = {total // count}")Type 3, 4 and 0, each on its own line. The output is:
count = 2
average = 3.5
integer part = 3In Python, / always gives a decimal answer, so the average is 3.5. The // operator is floor division. It drops the decimal part and rounds down, so 3 // 2 is 1 and -7 // 2 is -4. Use // only when a whole-number answer is what the task asks for.
Type 0 alone. The loop never runs, so count stays 0. The program then divides by zero and fails:
count = 0
Traceback (most recent call last):
File ".../main.py", line 9, in <module>
print(f"average = {total / count}")
~~~~~~^~~~~~~
ZeroDivisionError: division by zeroThe fix is to check the count before dividing. The version below prints a message when no numbers were given.
total = 0
count = 0
x = int(input())
while x != 0:
total += x
count += 1
x = int(input())
if count == 0:
print("no numbers were given")
else:
print(f"average = {total / count}")With the input 0, the output is no numbers were given. With the input 3, 4 and 0, the output is average = 3.5.
Worked example 3: lines of text and the word quit
Sentinels are often words, not numbers. A word is read with input() and then compared with the text of the sentinel. The check needs two small cleanups. The .strip() method removes spaces at both ends of the text. The .lower() method turns the text into lower case, so Quit and quit match.
lines = 0
line = input()
while line.strip().lower() != "quit":
lines += 1
print("got:", line)
line = input()
print("lines before quit:", lines)Type the lines apple, banana, Quit and ignored. The output is:
got: apple
got: banana
lines before quit: 2The loop stops at Quit. The line ignored is never read by the loop.
Python also has a shorter form for this pattern. The two-argument form of iter calls input again and again, and stops as soon as the call returns the sentinel value:
for line in iter(input, "quit"):
print("got:", line)
print("stopped")Type apple, banana, quit and ignored. The output is got: apple, got: banana, and then stopped. This form compares the text exactly, so Quit does not stop it. Use the while form when you need to ignore capital letters or spaces.
Comparing text with == and is
In Python, == compares the text of two strings. The is operator asks something else: whether two names point at the same object in memory. The example below shows the difference.
probe = "quit"
built = "".join(["qu", "it"])
print(built == probe)
print(built is probe)The output is:
True
FalseThe two strings hold the same text, so == is True. They are two separate objects, so is is False. Use == to compare text and numbers. Use is only to check for None, as in if value is None:. Whether two equal strings are the same object is a detail of how Python stores them, so do not depend on is for strings.
End of input
input() has no value to return when the input has ended. If you type the lines apple and then the input ends, without quit, the second call fails:
line = input()
while line.strip().lower() != "quit":
print("got:", line)
line = input()Give the input apple and then end it. The output is:
got: apple
Traceback (most recent call last):
File ".../main.py", line 4, in <module>
line = input()
EOFError: EOF when reading a lineEOFError means the input ended while the program was still waiting for a value. To read until the end of the input, loop over sys.stdin, the standard input stream. The loop ends by itself when the input ends, with no error. Exercise 4 uses this form.
Worked example 4: stop at the first thing that is not a number
Sometimes the input may contain a word where a number was expected. Python does not have a hasNextInt style method for input(). The usual tool is try and except. The try block runs the code. If the code raises an error, the except block runs instead of stopping the program. In this case the error is ValueError, which int() raises when the text is not a number.
total = 0
while True:
token = input()
try:
x = int(token)
except ValueError:
print(f"stopped at {token!r}")
break
if x == 0:
break
total += x
print(f"sum = {total}")Type 2, 3 and abc. The output is:
stopped at 'abc'
sum = 5The loop stopped at abc, and total is 5. The !r in the f-string shows the text with quotes, which makes an empty line or a space easy to see.
If you remove the try and except, the same input stops the program:
x = int(input())
print(x)Type abc. The output is:
Traceback (most recent call last):
File ".../main.py", line 1, in <module>
x = int(input())
ValueError: invalid literal for int() with base 10: 'abc'A common shortcut is the string method isdigit, which checks that every character is a digit. It fails for negative numbers, so it is not a full test:
print("-3".isdigit(), "7".isdigit())The output is False True. The text -3 is a valid number for int(), but isdigit says it is not. Use try and except to decide whether the text is a number.
Common mistakes
Skipping the priming read. If the first value is not read before the loop, the loop checks a name that has no value. Python stops with a NameError:
while x != 0:
x = int(input())The last line of the output is NameError: name 'x' is not defined. If the first value is read inside the body, the sentinel is handled as data (worked example 1).
Counting the sentinel. Check the sentinel before you count or add the value. Worked example 1 shows the count one too high.
Dividing by a count that can be zero. The program stops with ZeroDivisionError: division by zero when no values come before the sentinel (worked example 2). Check the count first.
Comparing strings with is. Two strings can hold the same text and still be different objects, so is can give False for equal text. Use == (worked example 3).
Running out of input before the sentinel. If the input ends and the loop still waits for a value, input() raises EOFError (worked example 3). Read the input with for line in sys.stdin: when the end of the input is the stop.
Crashing on text that is not a number. int("abc") raises ValueError. Wrap the conversion in try and except when the input may contain other text (worked example 4).
Choosing a sentinel that is also valid data. A sentinel of 0 cannot be used when 0 is a real value, such as a score that a student can actually get. Choose a value that never occurs as data, such as -1 for an age, or use a word such as quit.
Practice
Exercise 1. Read whole numbers until the value -1. Print how many of them are even. Type the values 4, 7, 10 and -1, each on its own line.
Show the answer
evens = 0
x = int(input())
while x != -1:
if x % 2 == 0:
evens += 1
x = int(input())
print(f"even count = {evens}")The output is even count = 2. The x % 2 == 0 check finds even numbers, because the remainder after dividing by 2 is 0 for an even number.
Exercise 2. Read words, one at a time, until the word done. Ignore capital letters in done. Print how many words came before it. Type the words red, blue and DONE, each on its own line.
Show the answer
count = 0
word = input().strip()
while word.lower() != "done":
count += 1
word = input().strip()
print(f"words before done = {count}")The output is words before done = 2. The .strip() call removes spaces at the ends of the word. The .lower() call makes DONE match done.
Exercise 3. Read whole numbers until 0 and print the largest. If the first value is 0, print no numbers. Test with the values 3, 9, 2 and 0, and then with the value 0 alone.
Show the answer
x = int(input())
if x == 0:
print("no numbers")
else:
largest = x
while x != 0:
if x > largest:
largest = x
x = int(input())
print(f"largest = {largest}")With the values 3, 9, 2 and 0, the output is largest = 9. With the value 0 alone, the output is no numbers. The first read is checked before the loop, so the loop never starts with the sentinel as its first value.
Exercise 4. Read lines of text until the input ends. Count the lines that are blank after removing spaces at the ends. Use the input lines a, an empty line, b (a space and the letter b) and a line with three spaces.
Show the answer
import sys
blank = 0
for line in sys.stdin:
if line.strip() == "":
blank += 1
print(f"blank lines = {blank}")The output is blank lines = 2. The .strip() method removes spaces and the line break at the end of each line. The loop stops by itself when the input ends, so no sentinel is needed. This is the end-of-input stop from worked example 3.
Summary
- A sentinel loop keeps reading until it sees a stop value, which is not data.
- Read the first value before the
whileloop, and read the next value at the end of the body. - Check the sentinel before counting or adding the value.
- Divide by a count only after checking that the count is not zero.
- Compare text with
==, never withis. - End of input is a second stop:
input()raisesEOFError, andfor line in sys.stdin:ends on its own.