Not graded. So why do it?
Earlier, we used an if
to decide on the tip rate:
- # Compute tip for a meal.
- # By Kieran Mathieson, June 14, Year of the Dragon
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
- tip = meal_cost * tip_rate / 100
- total = meal_cost + tip
- # Output
- print('-----------------')
- print('Meal cost: ' + str(meal_cost))
- print('Tip rate: ' + str(tip_rate) + '%')
- print('Tip: ' + str(tip))
- print('Total: ' + str(total))
Let's break it
Run the program again, but type an uppercase G for the service level. In the console:
- Meal cost? 60
- Service level (g=great, o=ok, s=sucked)? G
What's the tip rate? Why?

Ray
So, inputs of g
and G
are not the same. String ==
tests are case-sensitive.
Aye.
Note
String comparisons are case-sensitive. So "dog" and "Dog" are not equal to each other.
How do we fix that?

Georgina
You could have two if
s, but that doesn't feel right.
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'G':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- ...
That would work, but you're right, it isn't the best way to do it.
To see why, run the program again, but type "g " (a space at the end) instead of "g".
What happens? Why?

Ethan
Why would anyone type a space on the end?
Oh, never mind. It's natural to add a space to the end when you type. Just habit.
Aye. There's another reason, too. Double-click on the word "birds" below.
- Birds aren't real.
How many characters are selected?

Adela
I got six in Firefox, the letters and the space.
Right.
But in Notepad++, a popular Windows editor, the space isn't selected.

Ray
So, when a user double-clicks a word, then copies and pastes it, they might get a space, they might not.
We'd wind up with a lotta if
s, to test for "g", "G", "g ", "G "... more for every choice.
Aye, that's so.

Adela
Could we add code to make "G", "g ", "G ", whatevs, into "g"? Then there'd be just one thing to check, just "g".

Georgina
Ooo, that's a good idea! We'd have to get rid of the spaces, and make it lowercase.
This is sometimes called normalization. Converting data that can have many forms to just one, then having a simpler if
.
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- Convert service_level to lowercase, no spaces
- #Processing
- if service_level == 'g':
Python has a coupla things that will help.
strip
Remember, +
can append strings. We did this in the name tag program:
- full_name = first_name + ' ' + last_name
Python took the value in first_name
, appended a space, then appended the value in last_name
.
Try this in the console:
- '*' + ' doggos ' + '*'
Why did I ask you to put splats (*) at the start and end of the doggos
?

Ethan
Oh! We're learning how to strip spaces. The splats help us see the spaces around doggos
?
Right!
Now, try this in the console:
- '*' + ' doggos '.strip() + '*'
What happened?

Adela
No more spaces.
But why the strange way that's typed? With print
and input
, we put stuff in the parentheses, like print('Show this stuff')
. What's with putting .strip()
at the end?
print()
and input()
are functions that have been in Python for a long time.
However, .strip()
is a method. It's like a function belonging to a data type. .strip()
belongs to the string data type. It can be used on any string, but not on floats or ints.
Methods are part of object-oriented programming (OOP). OOP isn't part of this course, though. You won't be making your own methods.
Python's docs and tutorials are a bit confusing here. Sometimes methods are called functions. Ack!

Ray
So, how do we figure out what code to use for something?
I usually use my googly pal for that. I search for things I want to do, like:
- python remove extra spaces from string
- python square root
- python format a number to 2 decimal places
Often I find code to copy.

Georgina
The code we just tried...
- '*' + ' doggos '.strip() + '*'
We got *doggos*
. So strip()
got rid of the spaces. But the spaces are at the start and end of doggos
. Will strip()
get rid of spaces in the middle as well, like dogg os
?
Change this line of code...
- '*' + ' doggos '.strip() + '*'
... so that by looking at the results, you can tell whether strip()
removes spaces in the middle of a string.

Ethan
How about this? A space inside doggos
.
- '*' + ' dog gos '.strip() + '*'
Run it. Does strip()
remove all spaces from a string, or just spaces at the start and end?

Ray
Just at the start and end.
Aye.
Here's the code we started with.
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
Change the code so " g " (spaces before and after) works as well as "g" (spaceless).

Georgina
How about:
- service_level = input('Service level (g=great, o=ok, s=sucked)? ').strip()
- #Processing
- if service_level == 'g':
Yes, that works.

Adela
I had:
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip()
- #Processing
- if service_level == 'g':
Yep, that works, too.
Of the two alternatives above, which do you prefer?

Ethan
With the first one...
- service_level = input('Service level (g=great, o=ok, s=sucked)? ').strip()
... it's easy to miss the strip()
at the end. With...
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip()
- #Processing
- if service_level == 'g':
...it's more obvious the strip()
is there.
You could argue for both ways, but I agree with Ethan. I'd use the second. Making code easier to understand is a Very Important Thing.
More to do
OK, we're talking about normalization, taking some data and converting it to a normal form.
Why are we normalizing?

Georgina
So we can write simpler if
statements. Just one.
Right. Instead of this:
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- #Processing
- if service_level == 'g':
- Do something
- if service_level == 'g ':
- Do something
- if service_level == 'g ':
- Do something
- if service_level == ' g':
- Do something
- if service_level == ' g ':
- Do something
- ...
We write
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip()
- #Processing
- if service_level == 'g':
But we're not there yet. Run this code again, and type "G".
- # Compute tip for a meal.
- # By Kieran Mathieson, June 14, Year of the Dragon
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip()
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
- tip = meal_cost * tip_rate / 100
- total = meal_cost + tip
- # Output
- print('-----------------')
- print('Meal cost: ' + str(meal_cost))
- print('Tip rate: ' + str(tip_rate) + '%')
- print('Tip: ' + str(tip))
- print('Total: ' + str(total))
What happened? Why?
So, whether the user types "g" or "G", we want the if
statement to work.

Ethan
Idea! We have this.
- service_level = service_level.strip()
- #Processing
- if service_level == 'g':
Could we add something to make whatever the user types lowercase?
Aye, that's what we do.
Ask your googly friend how to convert to lowercase in Python.

Ray
There's another of those method things. lower
.
Aye!
- # Compute tip for a meal.
- # By Kieran Mathieson, June 14, Year of the Dragon
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip().lower()
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
- tip = meal_cost * tip_rate / 100
- total = meal_cost + tip
- # Output
- print('-----------------')
- print('Meal cost: ' + str(meal_cost))
- print('Tip rate: ' + str(tip_rate) + '%')
- print('Tip: ' + str(tip))
- print('Total: ' + str(total))
This...
- service_level = service_level.strip().lower()
... says to take what's in service_level
, strip
leading and trailing spaces, then convert what's left to lower
case, and then...
What's the last thing Python does to run this line of code?

Adela
Assignment. Put the result back into service_level
.
Yes, replacing whatever was there. You don't always want to do that. Sometimes you want to put the normalized version into a new variable. We'll see that later.
Normalization is so useful we should make a pattern out of it.
Simplify string validity checks by converting user input to a standard form.
Wrong letter
The code's looking good. Just one more thing. What should the program do if someone types X, or Q, or doggos? Something that's not one of the valid choices, even after normalizing.
What should the program do? It might help to think about what other software does when you input something that's unknown.

Adela
Well, if I'm typing something into a webform, I get an error message, like this:
Right! Let's show an error message.
Here's the code again:
- # Compute tip for a meal.
- # By Kieran Mathieson, June 14, Year of the Dragon
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip().lower()
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
- tip = meal_cost * tip_rate / 100
- total = meal_cost + tip
- # Output
- print('-----------------')
- print('Meal cost: ' + str(meal_cost))
- print('Tip rate: ' + str(tip_rate) + '%')
- print('Tip: ' + str(tip))
- print('Total: ' + str(total))
What do you change so the user gets an error message if service_level
isn't g, o, or s?

Georgina
Ooo! We could add to the if
, using else
to show an error.
Good! Maybe like this?
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- elif service_level == 's':
- tip_rate = 10
- else:
- print('Please enter G, O, or S.')
- tip = meal_cost * tip_rate / 100
Try it. What happened?
- Meal cost? 60
- Service level (g=great, o=ok, s=sucked)? doggos!
- Please enter G, O, or S.
- Traceback (most recent call last):
- File d:\documentz\python course\lessons\useful-calculators\service-level\service-level\service-level.py:18
- tip = meal_cost * tip_rate / 100
- NameError: name 'tip_rate' is not defined
Well, that didn't work. What now?
(Crickets)
Let's start by working out what the program did. How?

Ethan
We could use the debugger, see how the code's running.
Aye, makes sense. Before I run the code, I'm going to add a breakpoint.
Instead of running one line at a time, as we did before, we can tell the debugger to run the program normally, until it gets to the breakpoint. Then pause, before running the line with the breakpoint.
Add a breakpoint by clicking in the gray area as shown. Start debugging. When we started debugging before, Spyder stopped on the first line of the program. This time, Spyder notices the breakpoint, and runs until it reaches it. Allowing for user input, as we have.
So, I click the debug button. The program starts running. In the console, I type the meal cost as usual. Then the service level. I'll type, say, q and Enter.
Next, Spyder stops at the breakpoint. In the editor pane, I see:
It's hard to tell, but there's an arrow underneath the red circle.
The console shows the breakpoint:
The program is still running. You can tell because the square button is red. Normally, it's black.
Here's the Variable Explorer:
Here's the line we're about to run:
- tip = meal_cost * tip_rate / 100
Ray, can you break down that line, and tell me what it will do?

Ray
Sure. It's an =
, so Python will put whatever's on the right into the variable on the left.
Let's work out the right. meal_cost
is 60. OK, multiply that by tip_rate
, which is... Hey, no tip rate in the Variable Explorer!
Why is there no tip rate?
(Crickets.)

Georgina
Ooo! I see. I think. You typed q for service level. We can see that in the Variable Explorer:
Here's the code that makes tip-rate
:
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- elif service_level == 's':
- tip_rate = 10
- else:
- print('Please enter G, O, or S.')
- tip = meal_cost * tip_rate / 100
In line 2, service_level
isn't g, so skip to line 4. service_level
isn't o, so skip to line 6. service_level
isn't s, so skip to line 9, showing the error message. We skipped all the lines that set service_level
: lines 3, 5 and 7. So service_level
doesn't even exist, as the Variable Explorer shows.
Try to do the calculation, but there is no service_level
.
Right!

Ethan
Hey, nice going, Adela. I see what you mean.
What now?

Georgina
But, what do we do? We don't want to run the line:
- tip = meal_cost * tip_rate / 100
Not when there's an error.
Right. We could do a coupla things here.
- We could stop the program.
- We could keep asking the user to enter a valid value.
The first is easier. Let's do that. You'll learn how to do the second one later in the course.
Some new code:
- import sys
- ...
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- elif service_level == 's':
- tip_rate = 10
- else:
- print('Please enter G, O, or S.')
- sys.exit()
- tip = meal_cost * tip_rate / 100
Line 12 stops the program. Notice it's indented, so it belongs to the code block under the else
.

Georgina
What's the sys
for?
It tells Python where to find the exit
function.
Python is a powerful language. You can use it to write programs for:
- Data analysis
- Games
- Web applications
- Audio analysis
- Cryptography
- Text analysis
- A bazillion other things
The Python memory footprint would be huge if all the code for this stuff was loaded all at once.
Instead, Python has a core with the most important stuff, like variables, ifs, print()
, input()
, etc. If you want to do more, you load in modules. A module is a code library for a specific thing. For example, code for making games is in the pygame
module. Use the django
module to write web apps. pyaudio
is for audio analysis. And many more.
exit
is in the sys
module, perhaps the most used Python module. Load it by putting this at the top of your program:
- import sys
Call its exit
function like this:
- sys.exit()
The module name, then a .
, then the function in the module.
Here's the program so far:
- # Compute tip for a meal.
- # By Kieran Mathieson, June 14, Year of the Dragon
- import sys
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- service_level = service_level.strip().lower()
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- elif service_level == 's':
- tip_rate = 10
- else:
- print('Please enter G, O, or S.')
- sys.exit()
- tip = meal_cost * tip_rate / 100
- total = meal_cost + tip
- # Output
- print('-----------------')
- print('Meal cost: ' + str(meal_cost))
- print('Tip rate: ' + str(tip_rate) + '%')
- print('Tip: ' + str(tip))
- print('Total: ' + str(total))
Cleaner pattern use
We learned about the IPO pattern:
One of the simplest patterns:
- Code to put valid input data into variables.
- Code to work out anything output needs that isn't in the input.
- Code to output variable values.
Our code isn't as clean as it could be. It mixes input and processing.
How does the code mix I and P?

Ethan
Is it because the lines...
- print('Please enter G, O, or S.')
- sys.exit()
Handle input stuff, but are in the processing section of the code?
Right! Good work!

Ray
I don't get it. Input
gets input from the user, and Processing
checks it.
You could see it that way, but the pattern says Input
is "Code to put valid input data into variables." IPO programs are easier to change if each chunk is separate, if Processing
knows all the data it gets from Input
is valid. One less thing to think about when writing Processing
.
Here's another way to write the Input
chunk.
- ...
- # Input
- meal_cost = float(input('Meal cost? '))
- service_level = input('Service level (g=great, o=ok, s=sucked)? ')
- # Normalize service_level input
- service_level = service_level.strip().lower()
- # Validate service_level input
- if service_level != 'g' and service_level != 'o' and service_level != 's':
- print('Please enter G, O, or S.')
- sys.exit()
- #Processing
- if service_level == 'g':
- tip_rate = 20
- elif service_level == 'o':
- tip_rate = 15
- else:
- tip_rate = 10
- ...
Check the if
in Processing
(lines 13 to 18). It's back to its simpler form. If service_level
is not g, and it's not o, then it must be s. No need to test for s, if Processing
is guaranteed to get valid data from Input
.
Look at line 8. In English, it's "if the input is not g, and not o, and not s, it's invalid."
!=
is not equal to. var1 != var2
is true if the two vars are different.
and
is true if both sides are true.
You might think this would work:
- if service_level != 'g' and != 'o' and != 's':
Makes sense, since we're testing service_level
three times. However, compys aren't as smart as we are. You have to repeat service_level
in every comparison.
So, we have a good way of validating string inputs. Hooray!
Let's make a pattern of this, to make it easy to use in future code.
One-time string input validation
- Get user input into a string variable.
- Normalize it to simplify value checking.
- Check whether the value is one of the valid values.
- If not, show an error message and stop the program.
Summary
We worked on validation in this lesson.
- Normalizing converts data to a standard form.
- Normalizing input means we can use fewer
if
s. We don't have to check for 'g', ' g', ' G ', etc. - The
strip()
method removes spaces from the start and end of a string. - The
lower()
method converts a string to lowercase. - Use
print()
to show error messages. - Use @sys.exit() to stop a program.
- Do all validation in the Input chunk if you can.
Exercise
Is a TMNT?
The Turtles, by whittingtonrhett
Write a program that tells you whether a name the user inputs is a Teenage Mutant Ninja Turtle. Write the code so that case doesn't matter, and the program would still work if the user types extra spaces at the beginning or end of their input.
The TMNTs are:
- Leonardo
- Michaelangelo
- Donatello
- Raphael
Some I/O:
- Name? Leonardo
- TMNT Detector
- ==== ========
- Is Leonardo a TMNT? Yes
The user types spaces before a name:
- Name? Raphael
- TMNT Detector
- ==== ========
- Is Raphael a TMNT? Yes
Mixed-case with extra spaces at the end:
- Name? doNatEllo
- TMNT Detector
- ==== ========
- Is doNatEllo a TMNT? Yes
Not a turtle:
- Name? Kieran
- TMNT Detector
- ==== ========
- Is Kieran a TMNT? No
Match the output format. Note: when your code outputs Is XXX a TMNT?
, XXX is the user's input (with maybe extra spaces and mixed case), not normalized user input.
Upload a zip of your project folder. The usual coding standards apply.