<jupyter_start><jupyter_text>OverviewAuthored By: <NAME> and <NAME> Welcome to your Introduction to Python Workshop!Today will cover the basics in order to get you familiar with general coding concepts as well as Python syntax. Python is arguably one of the most user-friendly languages. The syntax is fairly general and easy to learn and a lot of the conditionals and loops that we write such as if statements read exactly as you would write them in english. Some beginner topics we will cover are:- Variables- Operations- If, Elif, and Else statements- For and While Loops- Writing a Function- Control FlowWith these concepts we will work through short examples so you can gain confidence with the topics. Towards the end of the workshop we will put everything we've learned together in order to write your own program! Let's start off with introducing you to the file type we are working with. Most files will end with the extension `.py` these signify that they are Python files and tell your computer what language to execute them in.  With this extension it is more likely that the code will exist in the file and when you execute it all of the output will print to a separate terminal window.This particular file is a `.ipynb` it is a type of Python file where both the input and the output are visible in the same file. You can press the play button on the left side of the code blocks to run the code they contain. Alternatively, you can click on a code block and hit `shift + enter` as a shortcut.  Coding Best PracticesEvery developer has their own sense of style when it comes to coding. That style can be whatever you like as long as it is clean and easy to read. The goal is to make your code organized in such a way that a new programmer could look at it and understand what is happening. A big portion of organizing is proper commenting. A comment is a part of your code, denoted by a `` that will not be run and shown in the output of your solution. Run the following block and you will see that only the second line will be visible in the output<jupyter_code># This is formatted as a comment
print("This is not a comment")<jupyter_output>This is not a comment<jupyter_text>Another important aspect is consistency. This includes things such as:- using tabs for indentation so they all match - always put variables in camelCase (where the first letter of each word is capitalized) or put them in snake_case (where there is an underscore between words)  Program Input and OutputInput is information that the user can provide to the program either by a prompt or with arguments to functions. Output is what is printed to the console for the user to be able to see.These concepts utilize Python's built in functions. This means that the function name and definition is saved within the python software and you don't need to define it yourself. You utilize a function by typing it's name followed by a set of round brackets that contain the appropriate arguments. Some examples of these functions that you will see used later on include:- print()- type()- input()  Output: Print Statements The `print()` function allows us to type a message that we want to user to see when the program is executed.<jupyter_code>print("Hello World!")<jupyter_output>Hello World!<jupyter_text>We can format our print statements by adding tabs or new lines by using these special characters:- To add a tab, use `\t`- To add a new line, use `\n`<jupyter_code>print("Hello\tWord")
print("Hello\nWorld")<jupyter_output>Hello	Word
Hello
World<jupyter_text>Input: Prompt User We can allow users to interact with our program by using the `input()` function. You can write a prompt in between the brackets to provide to the user.You can also save the answer the user provides by assigning it to a variable. We do this in python using the equal sign `=`. This means that we can use that variable to refer back to the user's answer later in our program.<jupyter_code># Prompting a user for input

name = input("Enter your name: ")
print("Hello", name)<jupyter_output>Enter your name: Emily
Hello Emily<jupyter_text>Data Types  Basic Data Types In Python there are variables which are items that are given a name so they can be stored in memory.All of these variables can have specific types that help define what they are and what operations they can undergo.``` A string is any number or characters including punctuation and spaces that are surrounding by quotation marks"Purple is my favourite Colour" An integer is any whole number 1238846372 A float is any number that includes decimals5.27992.00 A boolean are the True & False keywordsTrueFalse A list is items contained with [ ] and separated by commas["Purple", 238, 5.27, True] A dictionary is a collection of key:value pairs, we can use the key to find it's matching value.{1: "Purple", 2: "Yellow", 3: "Red"}```It's important to think about what type of variable you are working with so you can apply the proper types to it.<jupyter_code># First guess what the type should be and write it as a comment.
# Then run the next block to see if you were right!

a = 5

b = 2.0

print(a, "is of type", type(a))
print(b, "is of type", type(b))<jupyter_output>5 is of type <class 'int'>
2.0 is of type <class 'float'><jupyter_text>Type CastingIn the previous section we learned about all of the different data types within Python. We saw how we can use the `type()` function in order to discover what type a value corresponds to. Sometimes we want to change what the type of a variable is and we can do that with **type casting**. By wrapping the variable you want to update, similar to how you would use a function, you can change it's type. - To change a variable to an int: use int()- To change a variable to a string: use str()- To change a variable to a float: float()<jupyter_code>anInt = 890

print(type(anInt))

convertToString = str(anInt)

print(type(convertToString))<jupyter_output><class 'int'>
<class 'str'><jupyter_text>ListsLists are iterable. This means that you can go through each element in the list and apply a function or logic so that all items have had the effect. Lists can also be indexed. That means that you can specify what item you want back by using it's position. It is possible to have duplicates within a list as well.Positive numbers mean that you are counting from the front of the list back and negative numbers mean you are counting from the back of the list forward.<jupyter_code># List Indexing

myList = [5, 6, 23, 94, 12]

# What do you think the following indexes would output?

print(myList[0])

print(myList[-1])

print(myList[3])<jupyter_output>5
12
94<jupyter_text>You can also add to a list after it has already been created. This is done by using the `.append()` function.This function is different than the ones we've seen in earlier examples because we must supply a parameter within the brackets as well as before the command. `listOne.append("Hello")`This line of code says that we are going to add the word `"Hello"` to the end of the list named `listOne`.<jupyter_code># Appending to a list
listA = [1, 2, 3, 4]

print(listA)

# This line says:
# Add the number 5 at the end of listA
listA.append(5)

print(listA)<jupyter_output>[1, 2, 3, 4]
[1, 2, 3, 4, 5]<jupyter_text>It's also possible to add one list to the end of another. We do this using `.extend()`.<jupyter_code># Extending a list
listA = [1, 2, 3, 4]
listB = [5, 6, 7, 8]
print(listA)

listA.extend(listB)
print(listA)<jupyter_output>[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6, 7, 8]<jupyter_text>DictionariesDictionaries are a collection of items that are matched in key-value pairs. They are unordered which means they can't be searched and indexed the way a list can be so instead we access information from them by knowing the key for the value we are looking for. We can search for a value using a key but we cannot search for a key using a value. Every key that exists within a dictionary must be unique, this means there can't be any duplicates.In python we use square brackets to define a list [] and for dictionaries we define them using curly brackets {}. Similarly we separate each of the individual items using a comma, where each item or key-value pair is entered in the following form, key:value.<jupyter_code># Searching for a dictionary key
myDictionary = {1: "value", "key": 2}

# If I search for "key" it will return the value associated to that word
print(myDictionary["key"])<jupyter_output>2<jupyter_text>Now let's try and example where you can guess the output before running the next cell.<jupyter_code># Searching for multiple keys
randomDictionary = {18: "age", 2021: "year", "apple": "red", "banana": "yellow", "abc": 123, "favouriteNumber": 972}

print(randomDictionary["apple"])

print(randomDictionary["abc"])

print(randomDictionary[18])<jupyter_output>red
123
age<jupyter_text>Similar to a list you can also add to a dictionary after it has already been created. Since dictionaries cannot have duplicates if you were to assign a different value to a key that already exists it would overwrite the previous value.<jupyter_code>fruitDictionary = {"apple": "red", "banana": "yellow"}

# Add a value
fruitDictionary["grapefruit"] = "pink"
print(fruitDictionary)

# Overwrite a value
fruitDictionary["apple"] = "green"
print(fruitDictionary)<jupyter_output>{'apple': 'red', 'banana': 'yellow', 'grapefruit': 'pink'}
{'apple': 'green', 'banana': 'yellow', 'grapefruit': 'pink'}<jupyter_text>Operators**Operators** are special symbols that can be used to perform arithmetic and logical computations. We can use operators to evaluate mathematical expressions and make decision within our code. The symbols are called **operators**, and the values that the operator operates on are called **operands**.Let's check out some of the most common types of operators.  Arithmetic Operators Arithmetic operators are used to perform basic math. You can use multiple arithmetic operators in one statement, and just like regular math, these mathematical statements follow the order of BEDMAS.In this section, the terms *numeric values* and *numbers* refer to operands that are integers or floats.<jupyter_code># Overview of arithmetic operators

num1 = 10
num2 = 5

print("num1:", num1)
print("num2:", num2)

# Addition
num3 = num1 + num2
print("Addition: 10 + 5 =", num3)

# Subtraction
num4 = num1 - num2
print("Subtraction: 10 - 5 =", num4)

# Multiplication
num5 = num1 * num2
print("Multiplication: 10 * 5 =", num5)

# Exponents
num6 = num1 ** num2
print("Exponent: 10^5 =", num6)

# Modulus
num7 = num1 % num2
print("Modulus: 10 mod 5 =", num7)

# Simple division
num8 = num1 / num2
print("Division: 10 / 5 =", num8)<jupyter_output>num1: 10
num2: 5
Addition: 10 + 5 = 15
Subtraction: 10 - 5 = 5
Multiplication: 10 * 5 = 50
Exponent: 10^5 = 100000
Modulus: 10 mod 5 = 0
Division: 10 / 5 = 2.0<jupyter_text>**Tip**: If you want to use the result of a math operation inside the round brackets of a function, you do not need to assign it to a variable. Instead, you can simply provide the operation as the parameter.<jupyter_code>num1 = 1
num2 = 2
sum = 1 + 2

# What will the following lines print?

print(sum)
print(num1 + num2)
print(1 + 2)
print(num1 + 2)<jupyter_output>3
3
3
3<jupyter_text>We can use multiple operators in one statement, and the order of operations will follow BEDMAS just like in traditional mathematics.<jupyter_code># Order of operations

print(1 + 2 + 3)

# What will the following lines print?

print(2 + 5 * 2)
print((2 + 5) * 2) # Hint: the operation inside the brackets will take precedence<jupyter_output>6
12
14<jupyter_text>Addition and concatenation: `+`We can use the `+` operator to add two numbers together or put two strings together. We can add integers and floats together, including negative numbers.<jupyter_code># Numeric Addition

# Adding integers
int1 = 10
int2 = 20
print(int1 + int2) # This will result in an integer value

# Adding floats
float1 = 2.5
float2 = 3.66
print(float1 + float2) # This will result in a float value<jupyter_output>30
6.16<jupyter_text>We can also use the + operator to combine strings together. This is called called **concatenation**. Concatenation **appends** a string to another string. This means that the resulting value will be equal to the combined operand strings in the order that the operands are presented.<jupyter_code># String concatenation

hello_string = "Hello"
world_string = "World"
hello_word = hello_string + world_string

print(hello_word)

# If we don't need to store the concatenated string in a variable, we can:

# Using multiple strings separated by a comma as a parameter to the print function
# will automatically add spaces between the strings.
print(hello_string, world_string) 

# Or use the + operator to add any characters we want
print(hello_string + " " + world_string + "!")<jupyter_output>HelloWorld
Hello World
Hello World!<jupyter_text>Note that you cannot add two variables together if they are different data types, unless the variables are numeric such as floats and integers.<jupyter_code># Invalid addition example: adding a string and numeric value

str1 = "hello"
num1 = 4

# What do you think will happen when you uncomment the following line and run this code?
print(str1 + num1)<jupyter_output><empty_output><jupyter_text>We can use type casting to get around this problem.<jupyter_code>print("hello" + str(4))
str1 = "4"
int1 = 12
print(int(str1) + int1)<jupyter_output>16<jupyter_text>Subtraction: `-`The `-` operator can be used to subtract numbers. Unlike the `+` operator, we cannot use `-` with strings or other non-numeric data types.<jupyter_code># Subtraction

# Subtracting integers will result in an integer
int_diff = 10 - 5
print(int_diff)

# Subtracting floats will result in a float
float_diff = 10.5 - 2.75
print(float_diff)

# Subtracting integers from floats and vice versa will result in a float
int_float_diff = 10 - 2.0
print(int_float_diff)

# We can get a negative value as an answer as well
negative_diff = 2 - 10
print(negative_diff)<jupyter_output>5
7.75
8.0
-8<jupyter_text>We cannot use the `-` operator with non-numeric data types. The following examples will throw an error:<jupyter_code># Invalid subtraction example: attempting to subtract strings
# Uncomment lines 7 and 8 to view the error

rainbow_string = "Rainbow"
bow_string = "bow"

string_diff = rainbow_string - bow_string
print(string_diff)
# Invalid subtraction example: attempting to subtract lists
# Uncomment lines 7 and 8 to view the error

my_list1 = ['dog', 'cat', 'fish']
my_list2 = ['dog', 'cat']

list_diff = my_list1 - my_list2
print(list_diff)<jupyter_output><empty_output><jupyter_text>Multiplication: `*`We can use the `*` operator to multiply numbers, multiply strings by an integer, and multiply lists by an integer.<jupyter_code># Multiplication

# Multiplying integers will result in an integer
int_mult = 10 * 5
print(int_mult)

# Multiplying floats will result in a float
float_mult = 10.5 * 2.275
print(float_mult)

# Multiplying integers from floats and vice versa will result in a float
int_float_mult = 10 * 2.0
print(int_float_mult)<jupyter_output>50
23.8875
20.0<jupyter_text>Recall that concatenation is the process of appending values together.Multiplying a **string** value by a **positive integer** value *n* results in a concatenated string with *n* repetitions.<jupyter_code># Multiplying a string by an integer will result in a string with repetitions

hello_str = "Hello"
repeating_str = hello_str * 5
print(repeating_str)<jupyter_output>HelloHelloHelloHelloHello<jupyter_text>Multiplying a **list** by a **positive integer** value *n* results in a new list containing the original list's elements appended *n* times.Similarly to the string example above, you cannot use the `*` operator between a list and a non-integer value.<jupyter_code># Multiplying a list by an integer will result in a list with repeating elements

my_list = ["apple", "banana", "carrot"]
repeated_list = my_list * 3

print(repeated_list)<jupyter_output>['apple', 'banana', 'carrot', 'apple', 'banana', 'carrot', 'apple', 'banana', 'carrot']<jupyter_text>You cannot multiply a non-numeric value by a non-integer value.<jupyter_code># Invalid multiplication example: multiplying a string by a non-integer value
# Uncomment line 5 to view the error

hello_str = "Hello"
print(hello_str * 2.5)
# Invalid multiplication example: multiplying a list by a non-integer value
# Uncomment line 5 to view the error

my_list = ["apple", "banana", "carrot"]
print(my_list * 2.5)<jupyter_output><empty_output><jupyter_text>The `*` operator cannot be used on dictionaries because dictionaries are not allowed to contain duplicate keys.<jupyter_code># Invalid multiplication example: multiplying a dictionary by an integer value
# Uncomment line 5 to view the error

my_dict = {"a": "apple", "b": "banana", "c": "carrot"}
print(my_dict * 2)<jupyter_output><empty_output><jupyter_text>Division: `/` and `//`**The `/` operator is used to divide numeric values.**Performing this operation with two positive or negative numeric operands will always result in a **float**, even if both operands are integers.  **The `//` operator is used to divide numeric values and round the result down to the nearest integer.**If we want to ensure that the result of a division operation returns an **integer**, we can use the `//` operator to perform **floor division**.This operator performs regular division and then rounds the float value **down** to the nearest integer. Let's try dividing some numbers to see what happens.<jupyter_code># Simple division

int1 = 10
int2 = 4

simple_division1 = int1 / int2 
simple_division2 = int2 / int1

# What do you think the following lines will print?

print("Simple division:")
print("10 / 4 =", simple_division1)
print("4 / 10 =", simple_division2)<jupyter_output>Simple division:
10 / 4 = 2.5
4 / 10 = 0.4<jupyter_text>Now let's try performing floor division with the same numbers we used in the previous example.<jupyter_code># Floor division

int1 = 10
int2 = 4

floor_division1 = int1 // int2
floor_division2 = int2 // int1

# What do you think the following lines will print?

print("Floor division:")
print("10 // 4 =", floor_division1)
print("4 // 10 =", floor_division2)<jupyter_output>Floor division:
10 // 4 = 2
4 // 10 = 0<jupyter_text>Other arithmetic operatorsNow we have covered all of the most commonly used arithmetic operators.The following operators are beyond the scope of this introductory Python workshop, but we will briefly take a look at them. **Exponent**: `**`The `**` operator raises the left operand to the power of the right operand. This operator can only be used with numeric values.For example, the expression *x* \*\* *y* can be read as "*x* to the power of *y*."<jupyter_code># Exponentiation

print(2 ** 3)<jupyter_output>8<jupyter_text>**Modulus**: `%`The `%` operator returns the **remainder of a division operation** between two numeric values. In mathematics, this is referred to as the *modulus* or *mod* function.The expression *x* % *y* can be read as "the remainder of *x* divided by *y*."<jupyter_code># Modulus

print(10 % 4)<jupyter_output>2<jupyter_text>Shorthand NotationWhen we store a value in a variable, sometimes it is useful to directly update that variable by performing a mathematical operation on the value it currently holds. **Summary of Compound Operators*** `x += 1` is equivalent to `x = x + 1`  * `x -= 2` is equivalent to `x = x - 2`  * `x *= 3` is equivalent to `x = x * 3`   * `x /= 4` is equivalent to `x = x + 4`  * `x //= 5` is equivalent to `x = x // 5`   * `x **= 6` is equivalent to `x = x ** 6`    * `x %= 7` is equivalent to `x = x % 7`   Comparison OperatorsComparison operators are used to compare values. These operators will return a boolean value `True` or `False` based on whether the condition is met or not. **Equal to: `==`**Returns true if the left operand is equal to right operand. If they are not equal, it will return false. Be careful not to confuse this with a single `=`, which is used to assign variables!The `==` operator can be used on all data types, and it can also be used on mathematical operations.<jupyter_code># Equals

# Numeric values
print(3 == 100)
print(2.5 == 2.5)

# Strings
print("Bob" == "Bob")

# Lists
list1 = ["a", "b"]
list2 = ["c", "d"]
print(list1 == list1)
print(list1 == list2)

# Dictionaries
my_dict = {"a": "apple", "b": "banana", "c": "carrot"}
my_dict1 = {"a": "apple", "b": "banana", "c1": "carrot"}
print(my_dict == my_dict)
print(my_dict == my_dict1)

# Mathematical operations
print(3 * 4 == 2 * 6)<jupyter_output>False
True
True
True
False
True
False
True<jupyter_text>**Not equal to: `!=`**Returns true if the left operand does not equal the right operand. If they are equal, it will return false.<jupyter_code># Not equals

# Numeric values
print(3 != 100)
print(2.5 != 2.5)

# Strings
print("Bob" != "Mary")

# Lists
list1 = ["a", "b"]
list2 = ["c", "d"]
print(list1 != list1)
print(list1 != list2)

# Dictionaries
my_dict = {"a": "apple", "b": "banana", "c": "carrot"}
my_dict1 = {"a": "apple", "b": "banana", "c1": "carrot"}
print(my_dict != my_dict)
print(my_dict != my_dict1)

# Mathematical operations
print(3 * 4 != 2 * 6)<jupyter_output>True
False
True
False
True
False
True
False<jupyter_text>**Greater Than: `>`**Returns true if the left operand is greater than the right operand. If not, it will return false.**Greater Than or Equal To: `>=`**Returns true if the left operand is greater than or equal to the right operand. If not, it will return false.<jupyter_code># Greater Than
print(3 > 100) 
print(10 > 2.5)
print(10 > 10)

# Greater Than or Equal To
print(3 >= 100) 
print(10 >= 2.5)
print(10 >= 10)<jupyter_output>False
True
False
False
True
True<jupyter_text>**Less Than: `<`**Reurns true if the left operand is less than the right operand. If not, it will return false.**Less Than or Equal To: `<=`**Reurns true if the left operand is less than or equal to the right operand. If not, it will return false.<jupyter_code># Less Than
print(3 < 100) 
print(10 < 2.5)
print(10 < 10)

# Less Than or Equal To
print(3 <= 100) 
print(10 <= 2.5)
print(10 <= 10)<jupyter_output>True
False
False
True
False
True<jupyter_text>Logical Operators The logical operators `and`, `or`, and `not` are used to determine if multiple conditions are met.<jupyter_code># Logical Operators

x = 2 > 3 # This is False
y = 3 + 3 == 12 / 2 # This is True

print(x and y)
print(x or y)
print(not x)
print(not y)<jupyter_output>False
True
True
False<jupyter_text>Membership OperatorsThe membership operators `in` and `not in` are used to see whether or not an object contains another object.<jupyter_code># Membership Operators

my_list = ["a", "b", "c"]
user_input = input("Enter a letter: ")

if (user_input in my_list):
  print("Your letter is in the list!")
elif (user_input not in my_list):
  print("That letter is not in the list!")<jupyter_output><empty_output><jupyter_text>Now let's try combining logical and membership operators.<jupyter_code># Example of logical and membership operators

my_list = ["a", "b", "c"]
if ("x" in my_list and "a" in my_list):
  print("x and a are both in the list!")
elif ("x" in my_list or "a" in my_list):
  print("either x or a is in my_list, but not both!")
else:
  print("x and a are not in in my_list!")

if ("x" not in my_list and "a" in my_list):
  print("x isn't in my_list, but a is in my_list!")<jupyter_output>either x or a is in my_list, but not both!
x isn't in my_list, but a is in my_list!<jupyter_text>Flow ControlUp until now, every line of code in each of our examples was executed.Sometimes we want certain parts of our code to be executed only if certain conditions are satisfied. This concept is called **flow control**. There are three main types of statements that we can use to accomplish this:- If...else statements- For loops- While loops  If...else Statements If we want to run certain parts of code based on whether a condition is true or false, we can use `if`...`else` statements. Everything written under the `if` statement (i.e. the body) will be executed only if the expression is evaluated as true. Otherwise, that code will be skipped.<jupyter_code># If statement

if (100 > 20):
  print("This statement will be printed")

if (2 + 3 != 5):
  print("This statement will not be printed")<jupyter_output>This statement will be printed<jupyter_text>We can use the `else` statement to handle the case where the `if` statement is not met.<jupyter_code># If...else 

if (100 < 20):
  print("The statement was true!")
else:
  print("The statement was false!")<jupyter_output>The statement was false!<jupyter_text>We can be even more specific by using an `elif` statement, which stands for `else if`. Multiple `elif` statements can be used at one.<jupyter_code># If...elif...else

# Challenge: Change the values of num1 and num2 to execute different parts of this code!

num1 = 14
num2 = 1

if (num1 + num2 < 10):
  print("The if statement was executed")
elif (num1 + num2 == 15):
  print("The first elif statement was executed")
elif (num1 + num2 == 20):
  print("The second elif statement was executed")
else:
  print("The else statement was executed because none of the above conditions were met")<jupyter_output>The first elif statement was executed<jupyter_text>We can also put `if` statements inside othere `if` statements. These are called **nested `if` statements**.Pay close attention to your indentation when using nested statements!<jupyter_code># Nested if statements

# Challenge: Change the value of num to execute different parts of this code!

num = 4

if num != 0:
  if num > 0:
    print("The number is positive")
  else:
    print("The number is negative")
else:
  print("The number is 0")<jupyter_output>The number is positive<jupyter_text>For loops The **for loop** is used to iterate over a sequence of values and perform some actions on each of them. You can use it with specific data types such as **strings**, **lists**, and **dictionaries**. *Iterating* over an object means that each individual item inside the object will be operated on sequentially. For lists, each iteration will look at one element at a time. For strings, each iteration will look at one character at a time.<jupyter_code># Iterating over a list

pet_list = ["dog", "cat", "fish", "hamster"]

for pet in pet_list:
  print("I have a " + pet)
# Iterating over a string

hello_world = "Hello World!"

for character in hello_world:
  print(character)<jupyter_output>H
e
l
l
o
 
W
o
r
l
d
!<jupyter_text>When iterating over a dictionary, the variable after the "for" refers to the *key* of items inside the dictionary. We can then reference the values correspondinig to the keys with regular dictionary notation. Let's see how we can use this:<jupyter_code># Iterating over a dict

pet_dict = {"dog": "a", "cat": "b", "fish": "c", "hamster": "d"}

for pet in pet_dict.keys():
  print("I have a " + pet + " named " + pet_dict[pet])<jupyter_output>I have a dog named a
I have a cat named b
I have a fish named c
I have a hamster named d<jupyter_text>Sometimes we want to perform some operation a specific number of times. In order to use the *for* loop with integer values, we have to use the function `range(number_of_repetitions)`. This function computes the range of numbers between 0 and `number_of_repetitions`, but the value won't actually contain the number `number_of_repetitions` itself since the range starts at 0 instead of 1.Let's take a look at this function's behaviour:<jupyter_code># For loop

number = 10

for number in range(number):
  print(number)<jupyter_output>0
1
2
3
4
5
6
7
8
9<jupyter_text>While LoopsThe **while loop** is used to perform some actions only while a specified condition is `True`. You can use it with specific data types such as **strings**, **lists**, and **dictionaries**. If the condition given in the `while` statement is `True`, the body of the while loop will be executed. After the code is completed, the `while` condition is evaluated again. This process is repeated until the `while` condition is evaluated as `False`.Because of this, it is extremely important to ensure that the body of your while loop contains an **exit condition**. This means that your program has to evalue to False.<jupyter_code># While loop

num1 = 4
num2 = 12

while (num1 < num2):
  print(num1)
  num1 = num1 + 1
# An infinite loop - be careful to avoid this!

while True:
  print(".")<jupyter_output><empty_output><jupyter_text>Combining user input with flow controlNow that we've covered the basics, let's try to combine what you learned!<jupyter_code>first_name = input("Enter your first name: ")
last_name = input("Enter your last name: ")

full_name = first_name + " " + last_name
print("Hello", full_name)<jupyter_output>Enter your first name: 10
Enter your last name: 6
Hello 10 6<jupyter_text>By default, the data type of a user's input is a **string**, even if they enter numeric values (such as integers or floats) or boolean values (`true` or `false`). We can **cast** the input to a different data type so we can perform operations on it. We'll see how this works in the following example. Let's see how comparison operators can be used with user input:<jupyter_code># Running this code will cause an error

age = input("Enter your age: ")

# Let's see what happens when we try to use arithmetic operations on this user input:
if(age < 16): # Uncomment this line!
  print("Sorry, looks like you can't drive yet!")
else:
  print("Time to hit the road!")

# Now let's try casting the input to an integer so we can perform operations on it

age = int(input("Enter your age: "))

if(age < 16):
  print("Sorry, looks like you can't drive yet!")
else:
  print("Time to hit the road!")

# This code will accept an integer as input.

magic_number = 7

guess = int(input("Try to guess the magic number! "))
if (guess == magic_number):
    print("Congratulations You got it!")
elif (guess < magic_number):
    print("Your number is too low")   
else:
    print("Your number is too high!")

# Notice that we didn't need to perform the "greater than" comparison here.
# This is because we already covered all other possibilities
# (if the number was equal or too small)<jupyter_output>Try to guess the magic number! 10
Your number is too high!<jupyter_text>Now let's see how we can use `for` and `while` loops with user input:<jupyter_code># For loop
user_number = int(input("Enter a number: "))

for number in range(user_number):
  print(number)
# While loop

user_number = int(input("Enter a number: "))

counter = 0
while (counter < user_number):
  print(counter)
  counter = counter + 1<jupyter_output>Enter a number: 4
0
1
2
3<jupyter_text>Writing FunctionsA function is a way to group specific code lines together so that they perform a specific task when they are called. Functions make our program more organized because it lets us reuse a function over again by only calling it's name rather than copying all of the lines of code multiple times in different places. Functions can accept parameters which are variables that are used within the code of the function and it can return a value as well so it can be used in other parts of the program.  Function StructureEvery function will have a specific name. That name is what we will use to call it later on. When we call a function that means the code uses the function name and parameters within it such that it executes the function and performs the task it is built for.```def function_name(parameters):    lines of code    return True```- The keyword `def` marks that it is the start of a new function. - The function name is whatever unique identifier you choose. Most of the time it is a short description of the task the function performs.- Parameters are supplied in brackets but are optional.- The return statement can give back a value from the function so it can be used in other areas.It is important to remember that for a code line to be included in the function it has to be indented so that it shows up "underneath" or "inside" of the function definition header. This is normally done with a single tab.<jupyter_code># Example

def age(birth_year):
  """
  This function takes the user's birth year and returns their age
  """

  current_year = 2021
  user_age = current_year - birth_year 

  print("You are " + str(user_age) + " years old!")


# Now we call it so that we can see the output

age(1999)<jupyter_output>You are 22 years old!<jupyter_text>Indentation is very important! Anything you want included in your function has to be indented underneath otherwise the function won't know it belongs and issues will occur! This is the same concept as if statements and loops.<jupyter_code># Error related to indentation level
def age(birth_year):
  """
  This function takes the user's birth year and returns their age
  """

current_year = 2021
  user_age = current_year - birth_year 

  print("You are " + str(user_age) + " years old!")
# Error related to unknown variable since it isn't defined outside the function
def age(birth_year):
  """
  This function takes the user's birth year and returns their age
  """

  current_year = 2021
  user_age = current_year - birth_year 

print("You are " + str(user_age) + " years old!")<jupyter_output><empty_output><jupyter_text>Function ParametersParameters are values that are passed into the function so that it can be used within the code of the function. It is necessary to call the function with all the required parameters otherwise it will fail. This means if you define your function to have 2 variables but you only specify one it will not work.<jupyter_code># Purposely Broken Example

def favouriteThings(colour, food):
  
  print("Your favourite colour is: ", colour)

  print("Your favourite food is: ", food)

# If you only call the function with a colour it will fail
# Try running this block you will see an example error
favouriteThings("blue")
# Working Example 

def favouriteThings(colour, food):
    
    print("Your favourite colour is: ", colour)

    print("Your favourite food is: ", food)

favouriteThings("blue", "pizza")<jupyter_output>Your favourite colour is:  blue
Your favourite food is:  pizza<jupyter_text>If you run into a scenario where the value that is passed in as a parameter is usually a specific value you can choose to use a default value.A default value is when you set the variable equal to something so that the value is automatically selected and provided to the function. If you suddenly want to supply a different value that is still possible! If you supply the function definition with the value you would like it will override the default. Default arguments should always be at the end of the parameter list.<jupyter_code># Here you can see that the default of pizza will be printed
def favouriteThings(colour, food="pizza"):
    
  print("Your favourite colour is: ", colour)
  print("Your favourite food is: ", food)

print("Example #1")
favouriteThings("blue")

# Here I will override that with the variable I want to be used instead
def favouriteThings(colour, food="pizza"):
    
  print("Your favourite colour is: ", colour)
  print("Your favourite food is: ", food)

print("Example #2")
favouriteThings("blue", "pasta")<jupyter_output>Example #1
Your favourite colour is:  blue
Your favourite food is:  pizza
Example #2
Your favourite colour is:  blue
Your favourite food is:  pasta<jupyter_text>Return ValuesThe above example uses a `print` statement so that the user sees the answer whenever the function is called but we could instead use a `return` statement. A return statement would hold the value in memory instead of printing to the screen. This is valuable when you are doing intermediate calculations that the user doesn't need since they only want the end result.<jupyter_code># Example

def age(birth_year):
  """
  This function takes the user's birth year and returns their age
  """

  current_year = 2021
  user_age = current_year - birth_year 

  return "You are " + str(user_age) + " years old!"


# Now we call it we see on the first line there isn't an output
# For the second when the print statement is added it works
age(1999)

print(age(1999))

# You can also set the function equal to a variable and the data will be stored in that variable!

your_age = age(1999)
print(your_age)<jupyter_output>You are 22 years old!
You are 22 years old!<jupyter_text>Pass StatementWhen planning your program you may want to name all of the functions you will be using without filling each of them out right away.That is where a pass statement is important. It allows you to tell the program that it should skip this portion of the code.<jupyter_code>def working_function():
  pass
def broken_function():<jupyter_output><empty_output>