CIS 2020 · Python for Everyone · Module 6 · Study guide
The second half of Chapter 3 turns the if statement into a design skill. This module covers drawing a flowchart before you code one, choosing test cases that actually catch mistakes, the bool type and its operators, the string methods that let a program inspect its own input, and the validation loop that puts all of it to work keeping bad input out of a program.
Section 3.5
A flowchart is a picture of a decision before it becomes code. Draw the shapes first, and the if/elif/else almost writes itself.
A flowchart is a diagram that shows the steps of an algorithm and the order they run in, using a small, fixed set of shapes. It is a planning tool: you draw it before writing code, so you can catch a missing case or a backwards condition while it is still cheap to fix, not after you have already typed twenty lines of Python.
The simplest useful flowchart has one diamond. A theme park ride requires a minimum height; the flowchart reads the height, asks one yes-or-no question, and produces one of two messages.
Reading the shapes top to bottom is exactly the order the code runs in. Every arrow is one line of control flow; every diamond is one if:
height = int(input("Enter rider height in inches: "))
if height >= 48 :
message = "You can ride!"
else :
message = "Grow " + str(48 - height) + " more inches"
print(message)
When a problem has more than two outcomes, the flowchart chains decisions vertically: the No exit of one diamond leads straight into the next diamond, exactly the shape an elif chain takes in code.
elif does in code.hours = float(input("Enter hours parked: "))
if hours <= 1 :
fee = 5
elif hours <= 4 :
fee = 5 + 3 * (hours - 1)
else :
fee = 20
print("Fee: $" + str(fee))
Want more practice? Four more original flowcharts, including one with a nested decision and one that mirrors a full elif chain against a boolean condition, are worked out in More flowchart examples.
Section 3.6
A flowchart tells you the logic is right on paper. A test case tells you whether the code actually matches the flowchart.
A test case is one specific input, paired with the output you expect for it, used to check that a program behaves correctly. Good test cases are not random: they specifically target the places a decision is most likely to be wrong.
> instead of >=) only show up right at the boundary.Applying that to the roller coaster flowchart from Section 3.5, the boundary is 48 inches exactly:
| Test case | Input | Expected output | Why this input |
|---|---|---|---|
| Normal, tall enough | 60 | "You can ride!" | Well above the boundary; the easy case. |
| Boundary, exactly 48 | 48 | "You can ride!" | Tests that >= was used, not >. |
| One below the boundary | 47 | "Grow 1 more inches" | Confirms the switch happens at the right value, not one off. |
| Normal, too short | 30 | "Grow 18 more inches" | Checks the arithmetic in the No branch, not just the message. |
The boundary case is the one that actually catches bugs. A test suite made only of comfortable, normal-case inputs will pass even when >= was mistakenly typed as >. Only testing exactly 48 catches that.
Section 3.7
Every condition already produces a value. Section 3.7 gives that value a name: bool, and a small set of operators for combining them.
A bool is a data type with exactly two possible values, True and False. Every relational expression, such as age >= 18, evaluates to a bool, and that value can be stored in a variable just like an int or a str:
isAdult = age >= 18 print(isAdult) # True or False print(type(isAdult)) # <class 'bool'>
Three boolean operators combine or invert existing conditions. and is true only when both sides are true. or is true when at least one side is true. not flips a single condition to its opposite.
| A | B | A and B | A or B | not A |
|---|---|---|---|---|
| True | True | True | True | False |
| True | False | False | True | False |
| False | True | False | True | True |
| False | False | False | False | True |
temperature = 75
isRaining = False
if temperature >= 65 and not isRaining :
print("Good day for a picnic")
Never write if isValid == True :. isValid is already True or False; comparing it to True adds nothing and is considered poor style. Write if isValid : instead, and if not isValid : for the opposite, rather than if isValid == False :.
not binds tighter than and, which in turn binds tighter than or. When a condition mixes more than one operator, parentheses make the intent unmistakable even when they are not strictly required:
if not isMember and (age < 12 or age > 65) :
price = discountedPrice
Section 3.8
Before a program can validate a string, it has to be able to ask that string questions. Section 3.8 is the toolbox; Section 3.9 is what it gets used for.
Python compares strings the same way a dictionary orders words: character by character, using each character's underlying code point. This is called lexicographic order, and it means the relational operators you already know, <, >, ==, work on strings too:
print("apple" < "banana") # True -- 'a' comes before 'b'
print("apple" < "Apple") # False -- lowercase letters have larger code points than uppercase
Capital letters sort before every lowercase letter. "Zebra" < "apple" is True, which surprises almost everyone the first time. It has nothing to do with alphabetical order as a person understands it; it is purely comparing the numeric code point of 'Z' against the code point of 'a'.
A set of string testing methods each answer one yes-or-no question about a string's characters, returning a bool that can go straight into a condition:
| Method | True when… |
|---|---|
| .isdigit() | every character is a digit 0–9 |
| .isalpha() | every character is a letter |
| .isupper() | every letter in the string is uppercase |
| .islower() | every letter in the string is lowercase |
| .isspace() | every character is whitespace |
code = "A17" print(code.isdigit()) # False -- contains a letter print(code.isalpha()) # False -- contains digits
The in operator tests whether one string appears anywhere inside another, and .find() does the same search but returns the starting index, or -1 if nothing was found. len() gives the number of characters, and indexing or slicing pulls out a specific character or piece to examine on its own:
email = "[email protected]" print("@" in email) # True print(email.find("@")) # 7 -- the index where '@' starts print(len(email)) # 19 print(email[0]) # 's' -- the first character
Realistic string checks almost always combine more than one of these tools in a single condition, exactly the way Section 3.7's and combines two boolean values:
zipCode = input("Enter a 5-digit zip code: ")
if len(zipCode) == 5 and zipCode.isdigit() :
print("That looks like a valid zip code.")
else :
print("That does not look like a valid zip code.")
Neither check alone is enough. len(zipCode) == 5 would accept "AAAAA"; zipCode.isdigit() alone would accept "8". Only both together, joined with and, describe an actual 5-digit zip code.
Section 3.9
A decision that checks input once is not enough if the input can be wrong. Input validation keeps asking until the answer is usable, using exactly the string tools from Section 3.8 to decide what counts as usable.
Input validation is the practice of checking user input against the rules a program actually needs before doing anything with it, and re-prompting when the input breaks those rules. The standard pattern is a loop that keeps running exactly as long as the input is invalid:
age = int(input("Enter your age: "))
while age < 0 :
print("Age cannot be negative. Try again.")
age = int(input("Enter your age: "))
print("Age accepted:", age)
Read the loop the same way you read a flowchart: ask a question, and if the answer is still "invalid," loop back to the same question instead of moving on. The loop's condition is deliberately the opposite of the condition you actually want; the loop keeps running while the input is bad, and falls through the moment it becomes good.
The zip code check from Section 3.8 turns into a validation loop the same way: keep the same combined condition, just negate it and let the loop re-ask on failure.
zipCode = input("Enter a 5-digit zip code: ")
while len(zipCode) != 5 or not zipCode.isdigit() :
print("Please enter exactly 5 digits.")
zipCode = input("Enter a 5-digit zip code: ")
print("Zip code accepted:", zipCode)
Want more practice? Six more validation patterns, including checking for a non-empty string, catching a bad type with try/except, restricting input to a fixed menu of choices, and combining two rules at once, are worked out in More input validation examples.
elif chain does in code.> written where >= was needed.bool has exactly two values, True and False. Every relational expression already produces one.and needs both sides true, or needs at least one side true, not flips a single value. Precedence is not, then and, then or.if x == True : or if x == False :. Write if x : or if not x : instead..isdigit(), .isalpha(), in, .find(), len(), and slicing all let a program inspect what a string actually contains before deciding what to do with it.