https://ithaka-labs.s3.amazonaws.com/static-files/images/tdm/tdmdocs/CC_BY.png

Created by Nathan Kelber and Ted Lawless for JSTOR Labs under Creative Commons CC BY License
For questions/comments/improvements, email nathan.kelber@ithaka.org.


Python Basics 2

Description: This lesson describes the basics of flow control statements including:

and the basics of writing functions:

This is part 2 of 3 in the series Python Basics that will prepare you to do text analysis using the Python programming language.

Use Case: For Learners (Detailed explanation, not ideal for researchers)

Difficulty: Beginner

Completion Time: 90 minutes

Knowledge Required:

Knowledge Recommended: None

Data Format: None

Libraries Used: random to generate random numbers

Research Pipeline: None


Python Basics 2 image

Flow Control Statements

In Python Basics 1, you learned about expressions, operators, variables, and a few native Python functions. We wrote programs that executed line-by-line, starting at the top and running to the bottom. This approach works great for simple programs that may execute a few tasks, but as you begin writing programs that can do multiple tasks you’ll need a way for your programs to decide which action comes next. We can control when (or if) code gets executed with flow control statements. If a program is a set of steps for accomplishing a task, then flow control statements help the program decide the next action.

Flow control statements work like a flowchart. For example, let’s say your goal is to hang out and relax with friends. There are a number of steps you might take, depending on whether your friends are available or you feel like making some new friends.

Flowchart to hangout with friends

Each diamond in our flowchart represents a decision that has to be made about the best step to take next. This is the essence of flow control statements. They help a program decide what the next step should be given the current circumstances.

Boolean Values

One way we to create flow control statements is with boolean values that have two possible values: True or False. In our example above, we could consider a “Yes” to be “True” and a “No” to be “False.” When we have the data we need to answer each question, we could store that answer in a variable, like:

  • are_friends_available = False

  • make_new_friends = True

  • new_friend_available = True

This would allow us to determine which action to take next. When we assign boolean values to a variable, the first letter must be capitalized:

# Note, the first letter of a boolean value must always be capitalized in Python
are_friends_available = false
print(are_friends_available)
# The boolean values **True** and **False** cannot be used for variable names. 
# Treating the boolean value True as a variable will create an error
True = 7
# But we can store Boolean values in a variable

pizza_is_life = False
tacos_are_life = True

Comparison Operators

Now that we have a way to store integers, floats, strings, and boolean values in variables, we can use a comparison operator to help make decisions based on those values. We used the comparison operator == in Python Basics 1. This operator asks whether two expressions are equal to each other.

# Comparing two values with the comparison operator ==
67 == 67
# Note, a comparison operator uses ==
# Do not confuse with variable assignment statement which uses a single =
67 = 67

There are additional comparison operators that can help us with flow control statements.

Operator

Meaning

==

Equal to

!=

Not equal to

<

Less than

>

Greater than

<=

Less than or equal to

>=

Greater than or equal to

# Use the "Not equal to" operator
# Use the "equal to" operator with a string

A string cannot be equal to a float or an integer.

# Try using the "equal to" operator to compare a string with an integer

But an integer can be equal to a float.

# Try using the "equal to" operator to compare an integer with a float

We can use the comparison operator with variables.

# Using a comparison operator on a variable
# Create a variable number_of_dogs and assign the value of zero


# Check whether number_of_dogs is greater than or equal to 1

Boolean Operators (and/or/not)

We can also use Boolean operators (and/or/not) to create expressions that evaluate to a single Boolean value (True/False).

Using the Boolean Operator and

The and operator determines whether both conditions are True.

# If condition one is True AND condition two is True
# What will the evaluation be?
True and True
# If condition one is True AND condition two is False
# What will the evaluation be?
True and False

In order for an and expression to evaluate to True, every condition must be True. Here is the “Truth Table” for every pair:

Expression

Evaluation

True and True

True

True and False

False

False and True

False

False and False

False

Since and expressions require all conditions to be True, they can easily result in False evaluations.

Using the Boolean Operator or

The or operator determines whether any condition is True.

# Is expression one True OR is expression two True?
True or False
# Is condition one True OR is condition two True?
False or False

An or expression evaluates to True if any condition is True. Here is the “Truth Table” for every pair:

Expression

Evaluation

True or True

True

True or False

True

False or True

True

False or False

False

Since or expressions only require a single condition to be True, they can easily result in True evaluations.

Using the Boolean Operator not

Thenot operator only operates on a single expression, essentially flipping True to False or False to True.

# The not operator flips a True to False
not False

Combining Boolean and Comparison Operators

We can combine Boolean operators and comparison operators to create even more nuanced Truth tests.

# Evaluating two conditions with integers at once
(3 < 22) and (60 == 34) # What does each condition evaluate to?
# Evaluating two conditions with integers at once
(3 == 45) or (3 != 7) # What does each condition evaluate to?

So far, we have evaluated one or two conditions at once, but we could compare even more at once. (In practice, this is rare since it creates code that can be difficult to read.) Boolean operators also have an order of operations like mathematical operators. They resolve in the order of not, and, then or.

Writing a Flow Control Statement

The general form of a flow control statement in Python is a condition followed by an action clause:

In this condition:
       perform this action

Let’s return to part of our flowchart for hanging out with friends.

Flowchart showing if homework is yes then do assignment

We can imagine a flow control statement that would look something like:

if have_homework == True:
        complete assignment

The condition is given followed by a colon (:). The action clause then follows on the next line, indented into a code block.

  • If the condition is fulfilled (evaluates to True), the action clause in the block of code is executed.

  • If the condition is not fulfilled (evaluates to False), the action clause in the block of code is skipped over.

Code Blocks

A code block is a snippet of code that begins with an indentation. A code block can be a single line or many lines long. Blocks can contain other blocks forming a hierarchal structure. In such a case, the second block is indented an additional degree. Any given block ends when the number of indentations in the current line is less than the number that started the block.

Visualization of code block indentations

Since the level of indentation describes which code block will be executed, improper indentations will make your code crash. When using indentations to create code blocks, look carefully to make sure you are working in the code block you intend. Each indentation for a code block is created by pressing the tab key.

Types of Flow Control Statements

The code example above uses an if statement, but there are other kinds of flow control statements available in Python.

Statement

Means

Condition for execution

if

if

if the condition is fulfilled

elif

else if

if no previous conditions were met and this condition is met

else

else

if no condition is met (no condition is supplied for an else statement)

while

while

while condition is true

for

for

execute in a loop for this many times

try

try

try this and run the except code if an error occurs

Let’s take a look at each of these flow control statement types.

if Statements

An if statement begins with an expression that evaluates to True or False.

  • if True, then perform this action

  • if False, skip over this action

In practice, the form looks like this:

if this is True:
        perform this action

Let’s put an if statement into practice with a very simple program that asks the user how their day is going and then responds. We can visualize the flow of the program in a flowchart.

Flowchart of a good day program

Our program will use a single if statement. If the user types “Yes” or “yes”, then our program will send a response.

# A program that responds to a user having a good day
having_good_day = input('Are you having a good day? (Yes or No) ') # Define a variable having_good_day to hold the user's input in a string

if having_good_day == 'Yes' or having_good_day == 'yes':  # If the user has input the string 'Yes' or 'yes'
    print('Glad to hear your day is going well!') # Print: Glad to hear your day is going well!

Our program works fairly well so long as the user inputs ‘Yes’ or ‘yes’. If they type ‘no’ or something else, it simply ends. If we want to have our program still respond, we can use an else statement.

else Statements

An else statement does not require a condition to evaluate to True or False. It simply executes when none of the previous conditions are met. The form looks like this:

else:
        perform this action

Our updated flowchart now contains a second branch for our program.

The program flowchart with two branches

# A program that responds to whether the user is having a good or bad day
having_good_day = input('Are you having a good day? (Yes or No) ') # Define a variable having_good_day to hold the user's input

if having_good_day == 'Yes' or having_good_day == 'yes': # If the user has input the string 'Yes' or 'yes'
    print('Glad to hear your day is going well!') # Print: Glad to hear your day is going well!
    
# Write an else statement here

Our new program is more robust. The new else statement still gives the user a response if they do not respond “Yes” or “yes”. But what if we wanted to add an option for when a user says “No”? Or when a user inputs something besides “Yes” or “No”? We could use a series of elif statements.

elif Statements

An elif statement, short for “else if,” allows us to create a list of possible conditions where one (and only one) action will be executed. elif statements come after an initial if statement and before an else statement:

if condition A is True:
        perform action A
elif condition B is True:
        perform action B
elif condition C is True:
        perform action C
elif condition D is True:
        perform action D
else:
       perform action E

For example, we could add an elif statement to our program so it responds to both “Yes” and “No” with unique answers. We could then add an else statement that responds to any user input that is not “Yes” or “No”.

Flowchart showing three branches

# A program that responds to whether the user is having a good or bad day
having_good_day = input('Are you having a good day? (Yes or No) ') # Define a variable having_good_day to hold the user's input

if having_good_day == 'Yes' or having_good_day == 'yes': # If the user has input the string 'Yes' or 'yes'
    print('Glad to hear your day is going well!') # Print: Glad to hear your day is going well!

# Write and elif statement for having_good_day == 'No'


# An else statement that catches if the answer is not 'yes' or 'no'
else: # Execute this if none of the other branches executes
    print('Sorry, I only understand "Yes" or "No"') # Note that we can use double quotations in our string because it begins and ends with single quotes

The difference betweenelif and if?

When an elif condition is met, all other elif statements are skipped over. This means that one (and only one) flow control statement is executed when using elif statements. The fact that only one elif statement is executed is important because it may be possible for multiple flow control statements to evaluate to True. A series of elif statements evaluates from top-to-bottom, only executing the first elif statement whose condition evaluates to True. The rest of the elif statements are skipped over (whether they are True or False).

In practice, a set of mutually exclusive if statements will result in the same actions as an if statement followed by elif statements. There are a few good reasons, however, to use elif statements:

  1. A series of elif statements helps someone reading your code understand that a single flow control choice is being made.

  2. Using elif statements will make your program run faster since other conditional statements are skipped after the first evaluates to True. Otherwise, every if statement has to be evaluated before the program moves to the next step.

  3. Writing a mutually exclusive set of if statements can be very complex.

Expanding on the concept of our “How is your day going?” program, let’s take a look at an example that asks the user “How is your week going?” It will take two inputs: the day of the week (day_of_week) and how the user feels the week is going (having_good_week).

How is your week going flowchart

# A program that responds to the user's input for the day of the week and how their week is going.
day_of_week = input('What day of the week is it? ') 
having_good_week = input('Are you having a good week? ')

if day_of_week == 'Friday' or day_of_week == 'friday':
    print('Enjoy the weekend!')
    
elif having_good_week == 'Yes' or having_good_week == 'yes':
    print('I hope the rest of the week is good too!')
    
elif having_good_week == 'No' or having_good_week == 'no':
    print('Sorry to hear that. I hope the rest of the week is better.')
    
else:
    print('Sorry, I only understand "Yes" or "No"')

In the program above, try changing the elif statements to if statements. What happens if the user inputs ‘Friday’ and ‘Yes’?

while Loop Statements

So far, we have used flow control statements like decision-making branches to decide what action should be taken next. Sometimes, however, we want a particular action to loop (or repeat) until some condition is met. We can accomplish this with a while loop statement that takes the form:

while condition is True:
       take this action

After the code block is executed, the program loops back to check and see if the while loop condition has changed from True to False. The code block stops looping when the condition becomes False.

In the following program, the user will guess a number until they get it correct.

flowchart for number-guessing program

# A program that asks the user to guess a number

# The secret number is set here by the programmer.
secret_number = str(4) # We convert the integer to a string to compare easily with a user input string

# Ask the user to make a guess and take input in a string
guess = input('I am thinking of a number between 1-10. Can you guess it? ') # Take the user's first guess

# Check to see if the user guess matches our secret number
while guess != secret_number: # While the users guess does not equal secret_number
    guess = input('Nope. Guess again! ') # Allow the user to change the value of guess

print('You guessed the secret number, ' + secret_number) # Print a congratulations message with the secret number

Stopping Accidental Infinite Loops

When using a while loop, it is possible to accidentally create an infinite loop that never ends. This happens because the while condition never becomes False.

If you accidentally write code that infinitely repeats, you can stop the execution by selecting Interrupt from the Kernel menu. (Alternatively, you can press the letter i twice on your keyboard.) You may also want to remove the output of the rogue cell. You can do this from the Cell menu.

  • Clearing output from a single cell: CellCurrent OutputsClear

  • Clearing output from all cells: CellAll OutputClear

Clearing current outputs

# Run this infinite loop then interrupt the kernel
# Can you clear the cell output?
while True:
    print('Oh noes!')

A Repeating while Loop

In the program above, the while loop checked to see if the user guessed a particular number. We could also use a while loop to repeat a code block a particular number of times.

# A simple program that prints out 1, 2, 3
number = 0
while number < 3:
    number = number + 1 # We can also write an equivalent shortcut: number += 1
    print(number)
     

for Loop Statements with a range() Function

An abbreviated way to write a while loop that repeats a specified number of times is using a for loop with a range() function. This loop takes the form:

for i in range(j):
       take this action

where i is a generic variable for counting the number of iterations and j is the number of times you want the code block to repeat.

The starting value of i is 0. After each loop, i increases by one until it reaches j. The loop then stops. The variable names i and j are merely conventions. Using a different name may make the purpose of your code clearer to readers.

# A `for` loop that repeats 'What?' three times. 
# How could you make this repeat five times?
# What happens if you change the name of variable `i`?

for i in range(3):
    print('What?')
# A `for` loop that prints the value of the current iteration, here called `i`. 
for i in range(5):
    print(i)

In the examples above, the range() function takes a single argument that specifies the number of loops. The assumption is our range() function will start at 0, but we can specify any starting integer by adding an additional argument.

# A `for` loop that starts looping at 27 and stops looping at 32
for i in range(27, 32):
    print(i)
# A `for` loop that starts looping at -3 and stops looping at 7
for i in range(-3, 7):
    print(i)

We can also specify the size of each step for the range() function. In the above examples, the function adds one for each increment step, but we can add larger numbers or even specify negative numbers to have the range() function count down. The general form is:

for i in range(start, stop, increment):
       take this action

# A `for` loop that counts down from ten to one, followed by printing `Go!`
for i in range(10, 0, -1):
    print(i)
print('Go!')

Continue and Break Statements

while loops and for loops can also use continue and break statements to affect flow control.

  • A continue statement immediately restarts the loop.

  • A break statement immediately exits the loop.

Let’s return to our secret number guessing program. We will write the same program workflow using continue and break statements.

Flowchart for secret number guessing program

# A program that asks the user to guess a number

# Initialize the variables `guess` and `secret_number`
guess = 0
secret_number = str(4) # The secret number is set here by the programmer. Notice it is turned into a string so it can be easily compared with user inputs.

# Ask the user to make a guess
print('I am thinking of a number between 1-10.') 

# Test whether the user guess matches `secret_number`
while True:
    guess = input('What is your guess? ')
    if guess == secret_number:
        break
    else:
        continue
        
        
# After loop ends, print a congratulations message with the secret number       
print('You guessed the secret number, ' + secret_number) 

Exception Handling with try and except

When running code that may create an error, we can use try and except statements to stop a program from crashing.

# Try running the first code block, if there's an error then run the `except` code
# Divide 100 by a number the user chooses

user_number = input('100 divided by ... ')
try:
    print(100 / int(user_number))
except:
    print("You can't divide by zero.")

Coding Challenge! < / >

Using your knowledge of flow control statements, can you write a program that asks a user for their name, then prints out a response depending on whether they are old enough to drive? Check the end of this notebook for example solutions.


# Level 1 Challenge

# Ask the user for their name and store it in a variable `user_name`
# Ask the user for their age and store it in a variable `user_age`
# Write an if, elif, else statement that checks to see if the user is driving age.
# If the user is driving age, print out "user_name is old enough to drive."
# If the user is not driving age, print out "user_name is not old enough to drive."
# Level 2 Challenge

# Improve your flow control to consider whether the user has input a realistic age
# If the user inputs an age over 120, print "Is `user_name` a human or a turtle?"
# If the user inputs an age less than 5, print "`user_name` is not even ready for a bicycle!"
# Level 3 Challenge
# A program that checks to see if a user is old enough to drive.
# Verifies user has input a number and it is realistic.

# Find a solution to address when a user enters text that is not a number.

Functions

We have used several Python functions already, including print(), input(), and range(). You can identify a function by the fact that it ends with a set of parentheses () where arguments can be passed into the function. Depending on the function (and your goals for using it), a function may accept no arguments, a single argument, or many arguments. For example, when we use the print() function, a string (or a variable containing a string) is passed as an argument.

Functions are a convenient shorthand, like a mini-program, that makes our code more modular. We don’t need to know all the details of how the print() function works in order to use it. Functions are sometimes called “black boxes”, in that we can put an argument into the box and a return value comes out. We don’t need to know the inner details of the “black box” to use it. (Of course, as you advance your programming skills, you may become curious about how certain functions work. And if you work with sensitive data, you may need to peer in the black box to ensure the security and accuracy of the output.)

Libraries and Modules

While Python comes with many functions, there are thousands more that others have written. Adding them all to Python would create mass confusion, since many people could use the same name for functions that do different things. The solution then is that functions are stored in modules that can be imported for use. A module is a Python file (extension “.py”) that contains the definitions for the functions written in Python. These modules (individual Python files) can then be collected into even larger groups called packages and libraries. Depending on how many functions you need for the program you are writing, you may import a single module, a package of modules, or a whole library.

The general form of importing a module is: import module_name

You may recall from the “Getting Started with Jupyter Notebooks” lesson, we imported the time module and used the sleep() function to wait 5 seconds.

# A program that waits five seconds then prints "Done"
print('Waiting 5 seconds...')
import time # We import the `time` module
time.sleep(5) # We run the sleep() function from the time module using `time.sleep()`
print('Done')

We can also just import the sleep() function without importing the whole time module.

# A program that waits five seconds then prints "Done"
print('Waiting 5 seconds...')
from time import sleep # We import just the sleep() function from the time module
sleep(5) # Notice that we just call the sleep() function, not time.sleep
print('Done')

Writing a Function

In the above examples, we called a function that was already written. To call our own functions, we need to define our function first with a function definition statement followed by a code block:

def my_function():
       """Description what the functions does"""
       do this task

After the function is defined, we can call on it to do us a favor whenever we need by simply executing the function like so:

my_function()

After the function is defined, we can call it as many times as we want without having to rewrite its code. In the example below, we call my_function twice.

# Creating a simple function to double a number
def my_function():
    """Doubles any number the user enters"""
    outputnumber = int(inputnumber) * 2
    print(outputnumber)
    
inputnumber = input('I will double any number. Give me a number. ')
my_function()

inputnumber = input('Give me a new number. ')
my_function()

inputnumber = input('Give me one last number. ')
my_function()

Using functions also makes it easier for us to update our code. Let’s say we wanted to change our program to square our inputnumber instead of doubling it. We can simply change the function definition one time to make the change everywhere. See if you can make the change. (Remember to also change your program description in the first line!)

# Creating a simple function to raise a number to the second power.
def my_function():
    """Squares any number the user enters"""
    outputnumber = int(inputnumber) ** 2
    print(outputnumber)

inputnumber = input('I will raise any number to the second power. Give me a number. ')
my_function()

inputnumber = input('Give me a new number. ')
my_function()

inputnumber = input('Give me one last number. ')
my_function()

By changing our function one time, we were able to make our program behave differently in three different places. Generally, it is good practice to avoid duplicating program code to avoid having to change it in multiple places. When programmers edit their code, they may spend time deduplicating to make the code easier to read and maintain.

Parameters vs. Arguments

When we write a function definition, we can define a parameter to work with the function. We use the word parameter to describe the variable in parentheses within a function definition:

def my_function(input_variable):
       do this task

In the pseudo-code above, input_variable is a parameter because it is being used within the context of a function definition. When we actually call and run our function, the actual variable or value we pass to the function is called an argument.

# A program to greet the user by name

def greeting_function(user_name): #`user_name` here is a parameter since it is in the definition of the `greeting_function`
    print('Hello ' + user_name)

greeting_function('Sam') # 'Sam' is an argument that is being passed into the `greeting_function`

In the above example, we passed a string into our function, but we could also pass a variable.

# A program to greet the user by name

def greeting_function(user_name): #`user_name` here is a parameter since it is in the definition of the `greeting_function`
    print('Hello ' + user_name)

answer = input('What is your name? ')
greeting_function(answer) # `answer` is an argument that is being passed into the `greeting_function`

Function Return Values

Whether or not a function takes an argument, it will return a value. If we do not specify that return value in our function definition, it is automatically set to None, a special value like the Boolean True and False that simply means null or nothing. (None is not the same thing as, say, the integer 0.) We can also specify return values for our function using a flow control statement followed by return in the code block.

Let’s write a function for telling fortunes. We can call it fortune_picker and it will accept a number (1-6) then return a string for the fortune.

# A fortune-teller program that contains a function `fortune_picker`
# `fortune_picker` accepts an integer (1-6) and returns a fortune string

def fortune_picker(fortune_number): # A function definition statement that has a parameter `fortune_number`
    if fortune_number == 1:
        return 'You will have six children.'
    elif fortune_number == 2:
        return 'You will become very wise.'
    elif  fortune_number == 3:
        return 'A new friend will help you find yourself.'
    elif fortune_number == 4:
        return 'Do not eat the sushi.'
    elif fortune_number == 5:
        return 'That promising venture... it is a trap.'
    elif fortune_number == 6: 
        return 'Sort yourself out then find love.'

print(fortune_picker(5))

In our example, we passed the argument 3 that returned the string 'A new friend will help you find yourself'. To change the fortune, we would have to pass a different integer into the function. To make our fortune-teller random, we could import the function randint() that chooses a random number between two integers. We pass the two integers as arguments separated by a comma.

# A fortune-teller program that uses a random integer

from random import randint # import the randint() function from the random module

def fortune_picker(fortune_number): # A function definition statement that has a parameter `fortune_number`
    if fortune_number == 1:
        return 'You will have six children.'
    elif fortune_number == 2:
        return 'You will become very wise.'
    elif  fortune_number == 3:
        return 'A new friend will help you find yourself.'
    elif fortune_number == 4:
        return 'Do not eat the sushi.'
    elif fortune_number == 5:
        return 'That promising venture... it is a trap.'
    elif fortune_number == 6: 
        return 'Sort yourself out then find love.'

random_number = randint(1, 6) # Choose a random number between 1 and 6 and assign it to a new variable `random_number`
print(fortune_picker(random_number))

Local and Global Scope

We have seen that functions make maintaining code easier by avoiding duplication. One of the most dangerous areas for duplication is variable names. As programming projects become larger, the possibility that a variable will be re-used goes up. This can cause weird errors in our programs that are hard to track down. We can alleviate the problem of duplicate variable names through the concepts of local scope and global scope.

We use the phrase local scope to describe what happens within a function. The local scope of a function may contain local variable, but once that function has completed the local variable and their contents are erased.

On the other hand, we can also create global variables that persist at the top-level of the program and within the local scope of a function.

Ideally, Python programs should limit the number of global variables and create most variables in a local scope.

# Demonstration of global variable being use in a local scope
# The program crashes when a local variable is used in a global scope
global_secret_number = 7

def share_number():
    local_secret_number = 13
    print(f'The global secret number is {global_secret_number}')
    print(f'The local secret number is {local_secret_number} ')
    

share_number()
print(f'The global secret number is {global_secret_number}')
print(f'The local secret number is {local_secret_number}')

The code above defines a global variable global_secret_number with the value of 7. A function, called share_number, then defines a local variable local_secret_number with a value of 13. When we call the share_number function, it prints the local variable and the global variable. After the share_number() function completes we try to print both variables in a global scope. The program prints global_secret_number but crashes when trying to print local_secret_number in a global scope.

It’s a good practice not to name a local variable the same thing as a global variable. If we define a variable with the same name in a local scope, it becomes a local variable within that scope. Once the function is closed, the global variable retains its original value.

# A demonstration of global and local scope using the same variable name
secret_number = 7

def share_number():
    secret_number = 10
    print(secret_number)

share_number()
print(secret_number)

The Global Statement

A global statement allows us to modify a global variable in a local scope.

# Using a global statement in a local scope to change a global variable locally

secret_number = 7

def share_number():
    global secret_number # The global statement indicates this the global variable, not a local variable
    secret_number = 10
    print(secret_number)

share_number()
print(secret_number)

Lesson Complete

Congratulations! You have completed Python Basics 2. There is only one more lesson in Python Basics:

  • Python Basics 3

Start Next Lesson: Python Basics 3

Coding Challenge! Solutions

There are often many ways to solve programming problems. Here are a few possible ways to solve the challenges, but there are certainly more!

# Level 1 Solution
# A program that checks to see if a user is old enough to drive.

# Ask the user for their name and store it in a variable `user_name`
# Ask the user for their age and store it in a variable `user_age`
# Write an if, elif, else statement that checks to see if the user is driving age.
# If the user is driving age, print out "user_name is old enough to drive."
# If the user is not driving age, print out "user_name is not old enough to drive."

user_name = input('What is your name? ')
user_age = int(input('And what is your age? '))

if user_age >= 16:
    print(f'{user_name} is old enough to drive.')
elif user_age < 16:
    print(f'{user_name} is not old enough to drive.')
# Level 2 Solution
# A program that checks to see if a user is old enough to drive.
# Now with improved ability to check if the age number is realistic.

# Improve your flow control to consider whether the user has input a realistic age
# If the user inputs an age over 120, print "Is `user_name` a human or a turtle?"
# If the user inputs an age less than 5, print "`user_name` is not even ready for a bicycle!"

user_name = input('What is your name? ')
user_age = int(input('And what is your age? '))

if user_age > 120:
    print(f'Is {user_name} a human or a turtle?')
elif user_age < 5:
    print(f'{user_name} is not even ready for a bicycle!')
elif user_age >= 16:
    print(f'{user_name} is old enough to drive.')
elif user_age < 16:
    print(f'{user_name} is not old enough to drive.')
# Level 3 Solution
# A program that checks to see if a user is old enough to drive.
# Verifies user has input a number and it is realistic.

# Find a solution to address when a user enters text that is not a number.

user_name = input('What is your name? ')
user_age = None

while user_age == None:
    try:
        user_age = int(input('And what is your age? '))
    except:
        print('Please input a natural number for your age.')

if user_age > 120:
    print(f'Is {user_name} a human or a turtle?')
elif user_age < 5:
    print(f'{user_name} is not even ready for a bicycle!')
elif user_age >= 16:
    print(f'{user_name} is old enough to drive.')
elif user_age < 16:
    print(f'{user_name} is not old enough to drive.')
# Level 3 Solution (using continue/break)
# A program that checks to see if a user is old enough to drive.
# Verifies user has input a number and it is realistic.

# Find a solution to address when a user enters text that is not a number.

user_name = input('What is your name? ')

while True:
    user_age = input('What is your age? ')
    try:
        user_age = int(user_age)
        break
    except:
        print('Please input a number for your age.')
        continue
        
if user_age > 120:
    print(f'Is {user_name} a human or a turtle?')
elif user_age < 5:
    print(f'{user_name} is not even ready for a bicycle!')
elif user_age >= 16:
    print(f'{user_name} is old enough to drive.')
elif user_age < 16:
    print(f'{user_name} is not old enough to drive.')