Check the service level

Yay!

Multiple choice

What's a constant?

Saving
A

A variable that never changes.
For example, if the variable legs is set to 4,
it can never change in the rest of the code.

B

A value like 23, -9.01, or 'Doggos!'.
23 is always 23.

C

An if or while statement that always runs,
no matter what.

D

A letter that's not a vowel, like b or z.

Not graded. So why do it?

Earlier, we used an if to decide on the tip rate:

  1. # Compute tip for a meal.
  2. # By Kieran Mathieson, June 14, Year of the Dragon
  3.  
  4. # Input
  5. meal_cost = float(input('Meal cost? '))
  6. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  7.  
  8. #Processing
  9. if service_level == 'g':
  10.     tip_rate = 20
  11. elif service_level == 'o':
  12.     tip_rate = 15
  13. else:
  14.     tip_rate = 10
  15. tip = meal_cost * tip_rate / 100
  16. total = meal_cost + tip
  17.  
  18. # Output
  19. print('-----------------')
  20. print('Meal cost: ' + str(meal_cost))
  21. print('Tip rate: ' + str(tip_rate) + '%')
  22. print('Tip: ' + str(tip))
  23. 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
Reflect

What's the tip rate? Why?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ray
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
Georgina

You could have two ifs, 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".

Reflect

What happens? Why?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
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.
Reflect

How many characters are selected?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
Adela

I got six in Firefox, the letters and the space.

Right.

But in Notepad++, a popular Windows editor, the space isn't selected.

Notepad++ - five selected

Ray
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 ifs, to test for "g", "G", "g ", "G "... more for every choice.

Aye, that's so.

Adela: Aha!
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
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 ' + '*'
Reflect

Why did I ask you to put splats (*) at the start and end of the doggos ?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
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() + '*'
Reflect

What happened?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
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
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:

Often I find code to copy.

Georgina
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?

Reflect

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.

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
Ethan

How about this? A space inside doggos.

  • '*' + ' dog gos '.strip() + '*'
Reflect

Run it. Does strip() remove all spaces from a string, or just spaces at the start and end?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ray
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
Reflect

Change the code so " g " (spaces before and after) works as well as "g" (spaceless).

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
Georgina

How about:

  • service_level = input('Service level (g=great, o=ok, s=sucked)? ').strip()
  •  
  • #Processing
  • if service_level == 'g':

Yes, that works.

Adela
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.

Reflect

Of the two alternatives above, which do you prefer?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
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.

Reflect

Why are we normalizing?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
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".

  1. # Compute tip for a meal.
  2. # By Kieran Mathieson, June 14, Year of the Dragon
  3.  
  4. # Input
  5. meal_cost = float(input('Meal cost? '))
  6. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  7. service_level = service_level.strip()
  8.  
  9. #Processing
  10. if service_level == 'g':
  11.     tip_rate = 20
  12. elif service_level == 'o':
  13.     tip_rate = 15
  14. else:
  15.     tip_rate = 10
  16. tip = meal_cost * tip_rate / 100
  17. total = meal_cost + tip
  18.  
  19. # Output
  20. print('-----------------')
  21. print('Meal cost: ' + str(meal_cost))
  22. print('Tip rate: ' + str(tip_rate) + '%')
  23. print('Tip: ' + str(tip))
  24. print('Total: ' + str(total))
Reflect

What happened? Why?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.

So, whether the user types "g" or "G", we want the if statement to work.

Ethan
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.

Reflect

Ask your googly friend how to convert to lowercase in Python.

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ray
Ray

There's another of those method things. lower.

Aye!

  1. # Compute tip for a meal.
  2. # By Kieran Mathieson, June 14, Year of the Dragon
  3.  
  4. # Input
  5. meal_cost = float(input('Meal cost? '))
  6. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  7. service_level = service_level.strip().lower()
  8.  
  9. #Processing
  10. if service_level == 'g':
  11.     tip_rate = 20
  12. elif service_level == 'o':
  13.     tip_rate = 15
  14. else:
  15.     tip_rate = 10
  16. tip = meal_cost * tip_rate / 100
  17. total = meal_cost + tip
  18.  
  19. # Output
  20. print('-----------------')
  21. print('Meal cost: ' + str(meal_cost))
  22. print('Tip rate: ' + str(tip_rate) + '%')
  23. print('Tip: ' + str(tip))
  24. 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 lowercase, and then...

Reflect

What's the last thing Python does to run this line of code?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
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.

Pattern

Normalize a string

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.

Reflect

What should the program do? It might help to think about what other software does when you input something that's unknown.

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
Adela

Well, if I'm typing something into a webform, I get an error message, like this:

Error

Right! Let's show an error message.

Here's the code again:

  1. # Compute tip for a meal.
  2. # By Kieran Mathieson, June 14, Year of the Dragon
  3.  
  4. # Input
  5. meal_cost = float(input('Meal cost? '))
  6. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  7. service_level = service_level.strip().lower()
  8.  
  9. #Processing
  10. if service_level == 'g':
  11.     tip_rate = 20
  12. elif service_level == 'o':
  13.     tip_rate = 15
  14. else:
  15.     tip_rate = 10
  16. tip = meal_cost * tip_rate / 100
  17. total = meal_cost + tip
  18.  
  19. # Output
  20. print('-----------------')
  21. print('Meal cost: ' + str(meal_cost))
  22. print('Tip rate: ' + str(tip_rate) + '%')
  23. print('Tip: ' + str(tip))
  24. print('Total: ' + str(total))
Reflect

What do you change so the user gets an error message if service_level isn't g, o, or s?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
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
Reflect

Try it. What happened?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
  • 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
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.

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:

Stopped

It's hard to tell, but there's an arrow underneath the red circle.

The console shows the breakpoint:

Console

The program is still running. You can tell because the square button is red. Normally, it's black.

Here's the Variable Explorer:

Variables

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
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!

Reflect

Why is there no tip rate?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.

(Crickets.)

Georgina
Georgina

Ooo! I see. I think. You typed q for service level. We can see that in the Variable Explorer:

Variables

Here's the code that makes tip-rate:

  1. #Processing
  2. if service_level == 'g':
  3.     tip_rate = 20
  4. elif service_level == 'o':
  5.     tip_rate = 15
  6. elif service_level == 's':
  7.     tip_rate = 10
  8. else:
  9.     print('Please enter G, O, or S.')
  10. 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
Ethan

Hey, nice going, Adela. I see what you mean.

What now?

Georgina
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:

  1. import sys
  2. ...
  3. #Processing
  4. if service_level == 'g':
  5.     tip_rate = 20
  6. elif service_level == 'o':
  7.     tip_rate = 15
  8. elif service_level == 's':
  9.     tip_rate = 10
  10. else:
  11.     print('Please enter G, O, or S.')
  12.     sys.exit()
  13. 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
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:

  1. # Compute tip for a meal.
  2. # By Kieran Mathieson, June 14, Year of the Dragon
  3.  
  4. import sys
  5.  
  6. # Input
  7. meal_cost = float(input('Meal cost? '))
  8. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  9. service_level = service_level.strip().lower()
  10.  
  11. #Processing
  12. if service_level == 'g':
  13.     tip_rate = 20
  14. elif service_level == 'o':
  15.     tip_rate = 15
  16. elif service_level == 's':
  17.     tip_rate = 10
  18. else:
  19.     print('Please enter G, O, or S.')
  20.     sys.exit()
  21. tip = meal_cost * tip_rate / 100
  22. total = meal_cost + tip
  23.  
  24. # Output
  25. print('-----------------')
  26. print('Meal cost: ' + str(meal_cost))
  27. print('Tip rate: ' + str(tip_rate) + '%')
  28. print('Tip: ' + str(tip))
  29. print('Total: ' + str(total))

Cleaner pattern use

We learned about the IPO pattern:

Pattern

IPO

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.

Reflect

How does the code mix I and P?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ethan
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
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.

  1. ...
  2. # Input
  3. meal_cost = float(input('Meal cost? '))
  4. service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  5. # Normalize service_level input
  6. service_level = service_level.strip().lower()
  7. # Validate service_level input
  8. if service_level != 'g' and service_level != 'o' and service_level != 's':
  9.     print('Please enter G, O, or S.')
  10.     sys.exit()
  11.  
  12. #Processing
  13. if service_level == 'g':
  14.     tip_rate = 20
  15. elif service_level == 'o':
  16.     tip_rate = 15
  17. else:
  18.     tip_rate = 10
  19. ...

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.

Pattern

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 ifs. 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

Exercise

Is a TMNT?

Tina's Louie
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.