Programming Fundamentals (cont.)

Conditionals, Functions, Files, Exceptions, and working with Structured Data.


if Statements

Oftentimes in your program you want to decide which action to take based on a condition (e.g. is my list empty? is the temperature over a certain value? did I successfully connect to the database?). Python's if statements help you with that.


A Simple if Statement

cities = ["Toronto", "Vancouver", "Calgary", "Montreal"]

for city in cities:
    if city == "Calgary":
        print(f"{city} is the best city in Canada!")
    else:
        print(f"{city} is just meh!")

What's happening here?


Conditional Test

At the heart of every if statement is an expression that can be evaluated as True or False and is called a conditional test.

If a conditional test evaluates to True, Python runs the code inside the if block, and if the test evaluates to False, Python ignores it.

In the previous example, we used the equality operator (==) which returns either True or False (don't confuse it with the assignment operator =).

Good to know: Python Shell is a great tool to play around with conditional tests.


TRY IT YOURSELF

  • Open up a Python shell and try the following statements:

    city = "Calgary"
    city == "Calgary" 
    
    university = "UCalgary"
    university == "ucalgary" 
    university == "Ucalgary" 
    university == "UCalgary" 
    university.lower() == "ucalgary"
    

Inequality Operator (!=)

There are times you want to check for inequality instead of equality. For example, if the destination country is not Canada, then we can't ship there:

destination_country = "Italy"

if destination_country != "Canada":
    print("Sorry, but we don't ship there")

Mathematical Comparisons

Another group of statements that can be evaluated to True or False are mathematical comparisons.

>>> age = 19
>>> age < 21
True
>>> age <= 21
True
>>> age > 21
False
>>> age >= 21
False

What happens if you do this?

>>> 50 > "Calgary"
>>> 50 == "Calgary"

Checking Multiple Conditions

You can use the and or the or keyword to create more complex conditional tests.


and

For and, the whole conditional test will evaluate to True only if ALL the single tests evaluate to True.

>>> age = 28
>>> name = "Alice"
>>> age == 28 and name == "Alice"
True
>>> age == 29 and name == "Alice"
False
>>> age == 28 and name == "alice"
False
>>> job = "student"
>>> age == 28 and name == "Alice" and job == "student"
True

You can use optional parantheses to improve readability:

(age == 28) and (name == "Alice")

or

With or, the conditional test evaluates to True when at least ONE conditional test evaluates to True; otherwise, it will evaluate to False.

>>> age = 28
>>> name = "Alice"
>>> age == 28 or name == "Alice"
True
>>> age == 29 or name == "Alice"
True
>>> (age == 28) or (name == "alice")
True
>>> (age == 29) or (name == "alice")
False

Boolean Expressions

A Boolean expression is just another name for a conditional test. A Boolean value is either True or False, just like the value of a conditional expression after it has been evaluated.

Boolean values are often used to keep track of certain conditions, such as if a user is authorized to click a button, or if getting data from the database has finished:

user_authorized = False
data_gathering_finished = True

You can then use boolean values in if statements:

if user_authorized:
    # do something here
if data_gathering_finished:
    # do something here

Naming Best Practice for Booleans

When it comes to naming variables of type bool, it's convention to start their name with is_:

is_user_authorized = True
is_data_gathering_finished = False

if Statements (cont'd)

An if statement contains a conditional test (simple or complex) that can be evaluated to True of False.

if statements, like for loops, create a new indented block. The block can have one or more Python statements:

if age > 28:
    print("...")
    print("...")
    print("...")

if-else Statements

Sometimes, you want to take one or a series of actions when a conditions passes, and another set of actions when it doesn't. In this case, you can use an if-else statement:

age = 17
if age >= 18:
    print("You can get a driver's license")
else:
    print("You can't get a driver's license")

The if-elif-else Stetements

Sometimes, you need to test more than two possible situations. In this case you can use the Python if-elif-else syntax. Python executes only one block in an if-elif-else chain. Once a test condition passes, Python executes the block following that condition, and skips the rest:

age = 28
if age < 4:
    print("You get a kid discount")
elif age < 60:
    print("You get a normal adult fair")
else:
    print("You get a senior discount")

Multiple elif Blocks

You can have as many elif blocks as you want:

age = 45
if age < 4:
    print("You get a kid discount")
elif age < 39:
    print("You get a normal adult fair")
elif age < 65:
    print("You get an adult experiencing midlife crisis discount")
else:
    print("You get a senior discount")

Can you omit the else statement at the end?


Multiple if Statements

Sometimes, you want to check all the possible situations where more than one can be evaluated to True instead of just one. In this case, the if-elif-else statement doesn't help. But you can have multiple if statements one after another. Python checks every single one of them even if a previous if statement met the condition:

tax_credits = ["charity", "work-from-home", "self-employed"]

if "charity" in tax_credits:
    print("You get a Charitable Donation Tax Credit.")

if "work-from-home" in tax_credits:
    print("You get a Work from Home Tax Credit.")

if "self-employed" in tax_credits:
    print("You get Self-employment Expenses Tax Credit.")

What happens if you change the last 2 if with elif?


if and Lists

We'll check a few ways you can use if statements where the conditional test is related to a Python list.


Looking for a Value in a List

You can use the in keyword to see if a particular value is in a list. In case the value is indeed in the list, the conditional test will evaluate to True, otherwise, it will be False.

cities = ["Toronto", "Vancouver", "Calgary", "Montreal"]

if "Calgary" in cities:
    print("Now that's a good list!")
else:
    print("This list stinks!")

You can use not in to check if a value is not in a list:

if "Calgary" not in cities:

Check if a List is Empty

Python evaluates an empty list to False and a list with an item or more to True.

cities_to_visit = []
if cities_to_visit:
    for city in cities_to_visit:
        print(f"Let's go visit {city}!")
else:
    print("The list of cities to visits is empty. Let's staty home and eat pizza instead.")

Best Practices

Python evaluates certain values as False when in a boolean context. A quick "rule of thumb" is that all "empty" values are considered False: 0, None, [], {}, ''.

Use the "implicit" False or True if possible. They're easier to read and in most cases, they're also faster!

my_list = []

# good
if not my_list:
    print("list is empty")

# bad 
if len(my_list) == 0:
    print("list is empty")
if my_list == []:
    print("list is empty")

Best Practices

Although None evaluates to False, distinguish between the two as they're different in nature. None means an absence of a value, whereas False is a value:

a = None
b = []

# in order to check for None, use "is None" or "is not None"
if a is None:
    print("...")

# in order to check for empty/non-empty, use the implicit False value
if b:
    print("...")

This will make more sense when we talk about Python functions.


Functions

Functions are named blocks of code to do one specific job. When you want to perform a particular task that you've defined in a function, you call the function responsible for it. If you need to perform that task multiple times throughout your program, you don’t need to type all the code for the same task again and again; you just call the function dedicated to handling that task, and the call tells Python to run the code inside the function. You’ll see that using functions makes your programs easier to write, read, test, and fix.

You're also able to pass information to functions to change their behaviour. e.g. a function that adds a new record in a database table may get the record from the code calling it (the caller).


Defining a Function

# defining a function
def say_cheese():
    """Displays 'Cheese!'"""
    print("Cheese!")

# calling a function
say_cheese()

The def keyword tells Python that you're defining a function. This is what we call the function definition which tells Python:

  1. the name of the function
  2. what kind of information the function needs to do its job (the () holds that information)

All the indented lines that follow def say_cheese(): make up the body of the function.

The first line in the body of the function is called a docstring which is the function documentation. Python can use this docstring to generate documentation for the function (try say_cheese.__doc__).


TRY IT YOURSELF

Explain the following:

  • Function definition
  • Function body
  • Docstring
  • Function call
  • Function caller

Passing Info to a Function

Currently, the say_cheese() function only does one thing: saying 'Cheese!'. It's more common to have a function that does more than saying the same thing over and over. For instance, let's say we want a function that can say ANYTHING we want it to say. How can we do that?

def say_anything(thing):
    print(thing)

Here, we add thing to the function definition, which is a placeholder (or a variable) that can accept any value from the caller. We can now call the function and pass additional information:

say_anything("Ice cream so good.") # Ice cream so good.

Functions can accept more that one parameter.


Arguments vs Parameters

In the previous example, the variable thing in the definition of say_anything() is an example of a parameter, a piece of info that the function needs to perform it's job.

The value Ice cream so good. in say_anything("Ice cream so good.") is an example of an argument, a piece of info that's passed from a caller to a function.

Poeple use these terms interchangeably!


TRY IT YOURSELF

  • Write a function that accepts a social network name as a parameter named, social_network_name. The function should print that using the social network is a waste of time. Example: Using TikTok is a waste of time. Call the function 5-6 times with different social network names.

Passing Arguments

There are different ways to pass an argument to a function, which we'll be exploring next.


Positional Arguments

When you call a function, Python must match each argument in the function call with a parameter in the function definition. The simplest way to do this is based on the order of the arguments provided. Values matched up this way are called positional arguments:

def add_user(first_name, last_name):
    print(f"Adding {first_name} {last_name}")

add_user("Jiminy", "Cricket") # Adding Jiminy Cricket
add_user("Cricket", "Jiminy") # Adding Cricket Jiminy

Order matters in positional arguments.


Keyword Arguments

A keyword argument is a name-value pair that you pass to a function. You directly associate the name and the value within the argument, so when you pass the argument to the function, there's no confusion (you won't end up with Cricket Jiminy). In other words, you don't need to worry about correctly ordering the arguments in a function call this way:

def add_user(first_name, last_name):
    print(f"Adding {first_name} {last_name}")

add_user(last_name="Cricket", first_name="Jiminy") # Adding Jiminy Cricket

Order doesn't matter here anymore. Just make sure to use the exact names of the parameters in the function definition.


Default Values

You can define a default value for each parameter in a function definition. If an argument for a particular parameter is provided in the function call, Python will use the argument value, otherwise, it will use the parameter's default value.

def add_user(first_name, last_name="Smith"):
    print(f"Adding {first_name} {last_name}.")

add_user("John") # Adding John Smith.
add_user("John", "Green") # Adding John Green.

What happens if we define a default value for the first parameter?

def add_user(first_name="John", last_name):

Default Values Gotcha

When you use default values, any parameter with a default value needs to be listed after all the parameters that don't have default values. This allows Python to continue interpreting positional arguments correctly.

Put yourself in Python's shoes. What would you do in this situation?

def add_user(first_name="John", last_name):
    print(f"Adding {first_name} {last_name}.")

add_user("Alice") # Ambiguous!

Optional Arguments

We can use default values to make an argument optional.

def add_user(first_name, last_name, middle_name=''):
    if middle_name:
        print(f"Added {first_name} {middle_name} {last_name}.")

    else:
        print(f"Added {first_name} {last_name}.")

add_user("Chandler", "Bing")
add_user("Chandler", "Bing", "Muriel")

Argument Errors

When you provide fewer or more arguments than a function needs, Python will yell at you.

Try the following:

def add_user(first_name, last_name):
    print(f"Adding {first_name} {last_name}.")

add_user()
add_user("Chandler")
add_user("Chandler", "Muriel", "Bing")

Passing Arbitrary Number of Arguments

Sometimes, you're not clear on how many arguments will be sent to your function. Python allows a function to accept an arbitrary number of arguments from the caller.

def get_min(*numbers):
    return min(numbers)

min_number = get_min(-10, -12)
print(min_number) # -12

min_number = get_min(1, 10, -20, 56)
print(min_number) # -20

What is the type of numbers? Hint: use print(type(numbers)) inside the function.


Mixing Positional & Arbitrary Arguments

You can mix different types of arguments in Python. For instance:

def student_card(name, *grades):
    print(f"Student {name} has gotten these grades so far: {grades}")

student_card("John Nash", "A", "A+", "B", "C+")

Arbitrary Keyword Arguments

A function can also accept an arbitrary number of keyword arguments. Remember that keyword arguments are key-value pairs used in the caller when calling a function.

def create_profile(first, last, **user_info):
    user_info["first"] = first
    user_info["last"] = last 
    return user_info

user_profile = create_profile("Daenerys", "Targaryen", 
title_1="Mother of Dragons",
title_2="Breaker of Chains")
print(user_profile)

What's the type of user_info?


TRY IT YOURSELF

What happens when you run the following code?

def create_profile(first, last, **user_info):
    user_info["first"] = first
    user_info["last"] = last 
    return user_info

user_profile = create_profile("Daenerys", "Targaryen", 
"Mother of Dragons",
"Breaker of Chains")
print(user_profile)

Return Values

A function can take a value from inside a function and return it to the caller. Return values allow you to move much of your program’s grunt work into functions, which can simplify the body of your program.

Job Interview Worthy: In some languages like SQL, the difference between a function and a similar construct (like procedure) is that a function always returns a value.


Return a Simple Value

Let's change our add_user() function to return a value instead of printing to the output:

def add_user(first_name, last_name):
    """Returns a message about adding the user"""
    return f"Added {first_name} {last_name}."

message = add_user("Chandler", "Bing")
print(message) # Added Chandler Bing.

Return a Complex Value

You can also return a complex value, like a list, dictionary, set, or tuple.

def create_person(first_name, last_name, middle_name=""):
    """Returns a dictionary of data related to the person"""
    person = {}
    person["first_name"] = first_name
    person["last_name"] = last_name
    if middle_name:
        person["middle_name"] = middle_name

    return person

person = create_person("Ross", "Geller")
print(person)
# {'first_name': 'Ross', 'last_name': 'Geller'} 

Return Multiple Values

A function can return more than one values. To return return more than one values, separate them using ,s in the return statement.

To store the multiple return values, use the multiple assignments technique we saw earlier.

def get_area_and_perimeter(length, width):
    """Return area and perimeter of a rectangle"""
    area = length * width
    perimeter = 2 * (length + width)

    return area, perimeter


area, perimeter = get_area_and_perimeter(3, 7)
print(f"Area: {area} and Perimeter: {perimeter}")

Best Practice

Don't use more than two return values as that would make the code hard to read. In case you want to return more than two values, use a complex type, such as a dictionary or list.


Modifying Arguments in a Function

A function can modify the arguments it receives from the caller by changing their values. However, whether or not that change gets reflected to outside of the function, depends on a very important concept in Python: Mutability!

If a type is mutable, then any change happening inside a function will get reflect to the outside too. If a type is immutable, the change only stays inside the function.


Mutable vs Immutable Types

Immutable Types:

  • Numbers
  • Strings
  • Booleans
  • Tuples

Mutable Types:

  • Lists
  • Sets
  • Dictionaries

TRY IT YOURSELF:

Try the following programs and explain the output:

def change_me(param):
    param[1] = 5
    param.pop()

my_list = [1, 2, 3]
print(my_list)
change_me(my_list)
print(my_list)

my_list = [1, 2, 3]
change_me(my_list[:])
print(my_list)

TRY IT YOURSELF:

Try the following programs and explain the output:

def change_me(param):
    param[1] = 5

my_list = (1, 2, 3)
print(my_list)
change_me(my_list)
print(my_list)

TRY IT YOURSELF:

Try the following programs and explain the output:

def change_me(param):
    param = 5

number = 10
print(number)
change_me(number)
print(number)

Storing Functions in Modules

You can store your functions in a separate file called a module and then import that module into your main program.

This helps mainly in two ways:

  • Cleaner programs
  • Reusability

An import statement tells Python to make the code in a module available in the currently running program file.


Creating a Module

A module is just a Python file. Simply create a file with the .py extension and define a number of functions there. For example:

In math_utils.py:

def average(*numbers):
    """Returns the integer average of an arbitrary number of numbers"""
    total = sum(numbers)
    length = len(numbers)

    return total // length

Importing a Module (method 1)

You can import (copy) an entire module using import <filename>. For example, to import the module we just created, we can use import math_utils in another Python file to import the whole module (all its functions):

import math_utils

print(math_utils.average(1, 3, 4, 5))

Since we're importing the entire module, we need to specify which function we'd like to use by using the module name, followed by a ., followed by the function name.


Importing a Module (method 2)

We can also import specific functions from a module with the following syntax:

from math_utils import average

print(average(1, 3, 4, 5))

Since we imported a specific function, we don't need to use the . syntax anymore.


Importing a Module (method 2) Gotcha

Importing specific functions may cause conflicts if the main program also has functions with the same names.

Try this:

from math_utils import average

def average():
    print("hello world!")

print(average(1, 3, 4, 5))

Use as to Avoid Conflicts

In case you have conflicts with functions from another module, you can either use the first method of importing, or the second method with the as keyword to give the functions an alias (different name) to avoid conflicts.

from math_utils import average as avg

def average():
    print("hello world!")

print(avg(1, 3, 4, 5))

Importing a Module (method 3)

You can also import ALL the functions in a module using the following syntax:

from math_utils import *

print(average(1, 3, 4, 5))

Best Practice: it's best not to use this approach when you’re working with larger modules that you didn't write: if the module has a function name that matches an existing name in your project, you can get unexpected results.


User Inputs

There are times when you want to get some data from the end user of your application, such as their username or password. Python provides the input() function to help with such situations.


input() Function

The input() function pauses your program and waits for the user to enter some text. Once Python receives the user's input, it can assign it to a variable to make it convenient for you to work with:

color = input("What's your favourite colour?")
print(f"Your favourite color is {color}.")

The input() function takes only one argument which is the prompt you want to display to the user. It waits until the user presses ENTER.


TRY IT YOURSELF

How can you use a multiline prompt like the following for the input() function?

Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.

What... is your name?

Type of Inputs

Whenever we use the input() function in Python, what we receive from the user is always a string, regardless of what the user provides. In case we need to get a number from the user, such as their age, we need to make sure to cast the value into a number before using it.

Try the following:

age = input("How old are you?")
if age < 13:
    print("You must be at least 13 to use the application")

Casting Inputs

The previous code snippet will generate an error as you can't compare a number (13) to a string (age) in Python. In order to fix it:

age = input("How old are you?")
age = int(age)
if age < 13:
    print("You must be at least 13 to use the application")

while Loops

So far, we've seen how to use the Python for loop to go over a list or dictionary of items, or a range of numbers. The for loop takes a collection of items and executes a block of code once for each item in the collection. In contrast, the while loop runs as long as, or while, a certain condition is true.

counter = 1
while counter < 5:
    print(counter)
    counter += 1

What's the output?


while Loop and Inputs

Why are we talking about the while loop right after our discussion on the input() function? Because more often that not, you want to keep asking the user for information until they decide to end it (think of a chat bot).

prompt = "How can I help you?"
message = ""
while message != "exit":
    message = input(prompt)
    print(message)
    # do something here with the prompt

Infinite Loops

Infinite loops (or endless loops) are one the scariest things in programming. They run forever and don't let the program to progress to the next part; hence, the program gets stuck in one place. Consider the following code:

while True:
    message = input("How can I help you?")
    print(message)

# anything below is unreachable
print("out of the loop")
# rest of the program

Using break to Exit a Loop

In the previous example, we used the conditional test in the first line to exit the loop. Python (and all other popular programming languages) allows you to exit out of a loop at any time using the break keyword.

Let's fix the previous code:

while True:
    message = input("How can I help you?")
    if message == "exit":
        break
    print(message)

# anything below is unreachable
print("out of the loop")
# rest of the program

Using while Loop with Complex Data Types

You can use the while loop with lists and dictionaries as well.

movies = ["Interstellar", "V for Vendetta", "Kill Bill"]

while movies:
    this_movie = movies.pop(0)
    print(f"I'd like to watch {this_movie}.")

Best Practice

Avoid using the while loop if you can perform the same task with the for loop. while loops are more error-prone and harder to test. Consider the following code:

movies = ["Interstellar", "V for Vendetta", "Kill Bill"]

while movies:
    this_movie = movies[0]
    print(f"I'd like to watch {this_movie}.")

What's wrong with it? How can you fix it by replacing the while loop with a for loop?


TRY IT YOURSELF

  • Remove all instances of TikTok from the following list with only one loop.
social_apps = ["TikTok", "Twitter", "TikTok", "Instagram", "Facebook", "TikTok"]
  • Simulate the Internet Explorer browser that asks you Do you want Internet Explorer to be your default browser? repeatedly and doesn't stop until you say Yes or Shut up!.

Files

Learning a programming language is hardly complete without knowing how to handle files. Learning to work with files and save data will make your programs easier for people to use. You will be able to run your program, do some work, and then close the program and pick up where they left off.

Reading from a file is particularly useful in data analysis applications, but it's also applicable to any situation in which you want to analyze or modify information stored in a file.


Reading a File

We can read a file using the pathlib module (or library) that comes with every Python installation. Note that the file has to exist!

from pathlib import Path

path = Path("some_file.txt")
contents = path.read_text()
print(contents)

On Paths

When you pass a simple filename like some_file.txt to Path, Python looks in the directory where the file that's currently being executed (that is, your .py program file) is stored. But that's not always the case.

There are times where the file is located somewhere other than the current directory.


Relative Paths

A relative file path tells Python to look for a given location relative to the directory where the currently running program file is stored.


Relative Paths

my_project
├── my_program
│   ├── main.py
│   ├── files
│       ├── text_file.txt
├── another_file.txt
├── external_files
│   ├── yet_another_file.txt

Assuming we're running main.py, here's how we can use relative paths for the .txt files in the above structure:

  • text_file.txt: Path('files/text_file.txt')
  • another_file.txt: Path('../another_file.txt')
  • yet_another_file.txt: Path('../external_files/yet_another_file.txt')

Absolute Paths

You can also tell Python exactly where the file is on your computer, regardless of where the program that's being executed is stored. This is called an absolute file path.

We use absolute paths when a file isn't part of our project structure.


Absolute Paths

Absolute paths are usually longer than relative paths, because they start at your system's root folder (/).

/
├── home
│   ├── mkf
│       ├── .zshrc
  • .zshrc absolute path: /home/mkf/.zshrc

Reading a File Line by Line

We can use the splitlines() method on a Path result to read a text file line by line:

from pathlib import Path

path = Path("frankenstein.txt")
lines = path.splitlines()
for line in lines:
    print(line)

Since splitlines() returns a list of lines, we can use a for loop to run over the lines.


File Content Type

Just like reading a user input, Python treats text file content as string. If you're reading numbers, make sure to cast them to the appropriate type (int or float) before using.


TRY IT YOURSELF

Is your birthday in Pi?

  • Download 1 million digits of Pi from here and save it as a .txt file on your system.
  • Write a program that gets a birthday in the form of DDMMYY from the user (using the input() method) and checks to see if the birthday is included in Pi.

Hint: as a string is just a list of characters, you can use the in keyword to check if a substring exists in a string:

content = "best friend forever"
substring = "end"
if substring in content:
    print(f"There's '{substring}' in '{content}'")

Writing to a File

You can the same pathlib, but this time, the write_text() method to write to a file.

from pathlib import Path

path = Path('fav_movie.txt')
path.write_text("Interstellar")

If the file doesn't exist, write_text() will create the file first. Otherwise, it will erase it before writing new content in it!


TRY IT YOURSELF

  • You can write multiple lines into a file using the write_text() method. Find out how.

  • Write a program that prompts the user for their first name, last name, and date of birth, and writes each piece of data on a new line into a file named after the user's first name (e.g. john.txt)


Exceptions

Python uses special objects called exceptions to manage errors that arise during a program's execution. Whenever an error occurs that makes Python unsure of what to do next, it creates an exception object. If you write code that handles the exception, the program will continue running. If you don't handle the exception, the program will break.


Handling Exceptions

We can handle exceptions using the try-except block. A try-except block asks Python to do something, but it also tells Python what to do if an exception is raised.

When using try-except, the program, instead of breaking and showing the tracebacks, which is bad, users will see friendly error messages that you've written.

There are many situations in which an exception may happen. We'll see a number of them in this course.


Best Practice

When writing a Python program, make sure to read the documentation on the libraries you're using and find out about the potential exceptions that may arise. Make sure you understand them and handle them gracefully in your application.

As an exercise, read the following docs on the pathlib Python module: link and list all the exceptions.


ZeroDivisionError Exception

Try the following program:

print(10/0)

Let's fix it using a try-except block:

try:
    print(10/0)
except ZeroDivisionError:
    print("You can't divide numbers by 0! Did you skip math class in high school?")

FileNotFoundError Exception

If you try to read a file that doesn't exist, you'll get an exception. You can handle the situation with a try-except block.

from pathlib import Path

file_name = input("What file do you want me to read? ")
path = Path(file_name)
try:
    contents = path.read_text()
except FileNotFoundError:
    print("Sorry! The file doesn't seem to exist!")

else Block

You can have a third block besides try and except to handle the situation when no exception occurs:

from pathlib import Path

file_name = input("What file do you want me to read? ")
path = Path(file_name)
try:
    contents = path.read_text()
except FileNotFoundError:
    print("Sorry! The file doesn't seem to exist!")
else:
    print(contents)

Best Practice

The only code that should go in a try block is code that might cause an exception to be raised. Sometimes you'll have additional code that should run only if the try block was successful; this code goes in the else block. The except block tells Python what to do in case a certain exception arises when it tries to run the code in the try block.


Catch All Exceptions

You can catch any exception that may arise in a situation with the Exception type, which is the parent of all Exceptions.

try:
    print(10/0)
except Exception:
    print("You can't divide numbers by 0! Did you skip math class in high school?")

Best Practice

  • Don't catch Exception as it doesn't show the type of errors that may happen in the code. Using specific Exceptions such as ZeroDivisionError and FileNotFoundError are more readable.
  • Minimize the amount of code in a try-except block. The larger the body of the try, the more likely that an exception will be raised by a line of code that you didn’t expect to raise an exception. In those cases, the try-except block hides a real error.
  • Use else clause to continue the program following a successful try.
  • Use the finally clause to execute code whether or not an exception is raised in the try block. This is often useful for cleanups, like closing a database connection.

Structured Data

Structured data is data that has a standardized format for efficient access by software and humans alike. Depending on the standard, a standard may be more readable for a machine or a human being.

Examples of structured data:

  • CSV (Comma-Separated Values); more readable for machines. e.g.:
    name,age,occupation
    Ross Geller,32,Teacher 
    
  • XML (Extensible Markup Language); more readable for humans. e.g.:
    <person> 
        <name>Ross Geller</name>
        <age>32</age>
        <occupation>Teacher</occupation>
    </person> 
    

JSON

Or JavaScript Object Notation is another standard for storing and interchanging data that is kind of in the middle in terms of readability. That is, it's readable for humans to understand and for machines to parse and generate.

JSON is built on two structures:

  1. A collection of name/value pairs (think Python Dictionary)
  2. An ordered list of values (think Python List)

Values in JSON

Values in JSON can be one of the following types:

  1. string. e.g. "Pam Beesly"
  2. number. e.g. 5 or 10.6
  3. object (think Python Dictionary)
  4. array (think Python List)
  5. true
  6. false
  7. null

Keys are always string. i.e. in quotes.


JSON Example

{
    "name": "Ross Geller",
    "age": 32,
    "occupation": "Teacher",
    "good_at": ["Pivoting", "Science", "Divorce"],
    "is_on_a_break": true,
    "children": {
        "Ben": {"age": 8, "mother": "Carol"},
        "Emma": {"age": 1, "mother": "Rachel"}
    },
    "currently_married_to": null,
}

Whitespace in JSON

Whitespace is ignored in JSON and doesn't matter. The followings are the same as far as a machine is concerned, but one is obviously more readable for humans.

{"name":"Ross Geller","age":32,"occupation":"Teacher"}
{
    "name": "Ross Geller",
    "age": 32,
    "occupation": "Teacher"
}

Use this website to beautify/uglify JSON documents.


Programming with JSON

JSON is the most popular standard for storing and interchanging data between machines. As such all major programming languages (such as Python) come with built-in support for generating/parsing JSON documents.


Python & JSON

Python has a built-in module (library) to work with JSON named json. We start by importing the module using import json first.

There are two main method for parsing (or reading) and generating (or writing) JSON documents:

  • json.loads() for parsing JSON documents
  • json.dumps() for generating JSON documents

Reading Example

import json

document = '{"name":"Ross Geller","age":32,"occupation":"Teacher"}'

# this will convert the document into a Python dictionary
person = json.loads(document)

print(person["name"])
print(person["age"])
print(person["occupation"])

Reading Example

import json

document = '["Carol", "Emily", "Rachel"]'

# this will convert the document into a Python list 
the_gellers = json.loads(document)

for wife in the_gellers:
    print(f"Ross has been married to {wife}.")

TRY IT YOURSELF

What happens when you try to parse (or load) invalid JSON? How can you fix it?

document = '["Carol", "Emily", "Rachel"'

# this will convert the document into a Python list 
the_gellers = json.loads(document)

for wife in the_gellers:
    print(f"Ross has been married to {wife}.")

Writing Example

You normally want to turn either a Python list or dictionary into JSON (although you can turn other Python types too, but that's not common as it doesn't really make sense).

import json
person = {
    "name": "Ross Geller",
    "age": 32,
    "occupation": "Teacher"
}

document = json.dumps(person)
print(document)

Writing Example

import json
person = {
    "name": "Ross Geller",
    "is_on_a_break": True,
    "currently_married_to": None,
}

document = json.dumps(person)
print(document)

What does True and None convert to in JSON? What happened to the trailing , at the end of the last field?


TRY IT YOURSELF

  • Read a JSON document from a file and parse it in a Python program.

  • Write a JSON document to a file in a Python program.

External Modules

External Modules (or libraries or packages) are modules that are not included in the Python standard library and need to be downloaded and installed first before being used.


pip

pip is the package installer for Python. You can use pip to install external modules. pip can install, reinstall, or uninstall Python packages.

pip is part of your Python installation, so if you have Python installed on your system, you should have pip as well.

To make sure, run pip --version or pip3 --version in a terminal.


pip Cheat Sheet

  • Install the latest version: pip install package-name
  • Install a specific version: pip install package-name==1.2.3
  • Upgrade a package to latest version: pip install --upgrade package-name
  • Uninstall a package: pip uninstall package-name
  • List all installed packages: pip list
  • Search for a package: pip search package-name
  • Updating pip itself: python -m pip install --upgrade pip

pip install Examples

  • pip install requests. library for making HTTP requests.
  • pip install tensorflow. library for machine learning stuff.
  • pip install scipy. library for science stuff.
  • pip install numpy. library for working with multi-dimensional arrays and matrices.
  • pip install matplotlib. library for visualization.