Skip to article frontmatterSkip to article content

Discussion 1: Introduction to Python

Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license. (c) Kyle T. Mandli
from __future__ import division, print_function

Note to lecturers: This notebook is designed to work best as a classic Jupyter Notebook with nbextensions

  • hide_input: to hide selected python cells particularly for just plotting

  • RISE: Interactive js slide presentations

Discussion 1: Introduction to Python

So you want to code in Python? We will do some basic manipulations and demonstrate some of the basics of the notebook interface that we will be using extensively throughout the course.

Objectives:

  • Provide overview of simplest data types and flow control available in Python 3

  • Provide a few practice problems

  • set up for Homework 0 to debug the homework submission system and introduce working with Jupyter notebooks

Topics:

  • Math

  • Variables

  • Lists

  • Control flow

  • Coding style

  • Other data structures

  • Jupyter notebooks/Jupyter Lab

There is considerable online documentation and tutorials for python.

Other intros:

Python Math

Lets start with some basic operations:

2 + 2
32 - (4 + 2) ** 2
1 / 2
4.0 + 4.0 ** (3.0 / 2.0)

Note: See full list of operators supported in python here

Good practice to just add a decimal after any number you really want to treat as a float.

Additional types of numbers include complex, Decimal and Fraction.

3 + 5j

Note that to use “named” functions such as sqrt or sin we need to import a module so that we have access to those functions. When you import a module (or package) in Python we are asking Python to go look for the code that is named and make them active in our workspace (also called a namespace in more general parlance). Here is an example where we use Python’s builtin math module:

import math

math.sqrt(4)
math.sin(math.pi / 2.0)
math.exp(-math.pi / 4.0)

Note that in order to access these functions we need to prepend the math. to the functions and the constant π\pi. We can forgo this and import all of what math holds if we do the following:

from math import *

sin(pi / 2.0)

Notes:

  • import * is discouraged, particularly if you only need a few functions or you will be mixing with other modules that define sin, for example numpy.sin() and math.sin() have somewhat different functionality.

  • if you only want a few functions from math use from math import sin, cos

  • many of these functions always return a float number regardless of their input.

Variables

Assign variables like you would in any other language:

num_students = 126
room_capacity = 120
(room_capacity - num_students) / room_capacity * 100.0

As indicated in the previous section, there are many different data types. For example, a variable could be an integer, a floating point number, a string, or numerous other basic types. Python will determine the data type based on how you enter it.

In the following example, three different variables are defined, and the type associated with each variable is printed.

number = 5
ratio = 0.15
description = "The ratio is"
doit = False

print(number, type(number))
print(ratio, type(ratio))
print(description, type(description))
print(doit, type(doit))

The data type can be explicitly defined using the float() and int() commands.

number = int(5.0)
ratio = float(0.15)
print(number, type(number))
print(ratio, type(ratio))

Note: if you are testing for the type of a python object you should use isinstance

x = True
if isinstance(x, str):
    print("{} is a string".format(x))
else:
    print("{} is not a string, it is type: {}".format(x, type(x)))

One thing to be careful about is that Python is case sensitive.

N = 20
n = 10
print(N, n)

Lists

One of the most useful data structures in Python is the list.

grades = [90.0, 67.0, 85.0, 76.0, 98.0, 70.0]

Lists are defined with square brackets and delineated by commas. Note that there is another data type called sequences denoted by ( ) which are immutable (cannot be changed) once created. Lets try to do some list manipulations with our list of grades above.

Access a single value in a list

print(grades)
grades[1]

Note that Python is 0 indexed, i.e. the first value in the list is accessed by 0. Reverse indexing is done using negative value starting from -1 which corresponds to the last element

grades[-2]

Find the length of a list

len(grades)

There are multiple ways to append values into a list.

print(grades)
grades = grades + [62.0, 82.0, 59.0]
print(grades)
grades.append(88.0)
print(grades)

You can use the standard indexing method shown above to change a value within the array.

grades[1] = 68.0
print(grades)

Slicing is another important operation

print(grades)
grades[2:5]
grades[0:4]
grades[:4]
grades[4:]

Note that the range of values does not include the last indexed! This is important to remember for more than lists but we will get to that later.

grades[4:11]

Another property of lists is that you can put different types in them at the same time. This can be important to remember if you may have both int and float types.

remember = [int("2"), 2, 2.0, "2.0"]
print(remember)

A list can also hold any data type or data structure inside it, for example a list inside a list (referred to as nested lists) is helpful in defining matrices (although we will find a better way to do this later).

matrix_a = [[1], [2], [3]]

Finally, one of the more useful list creation functions is range which creates a list with the bounds requested. This creates a special type within Python, but it acts like an array.

values = range(3, 7)
print(values, type(values))
print(values[0], values[1], values[2], values[-1])
for i in range(3, 7):
    print(i)

Comments

Comments can be added to code using the # character. Anything after # on a line is ignored.

# Set up the parameters associated with the partition
N = 10  # Number of partitions to use
b = 1.0  # The right endpoint of the interval
a = 0.0  # The left endpoint of the interval
delta_x = (b - a) / float(N)  # The width of each interval
print("The interval width is {0}".format(delta_x))

The python style guide PEP 8 however discourages in-line comments

Control Flow

In this section a number of different ways to control and define which commands are executed are given. The commands include conditional expressions like ‘if’ blocks that decide individual sets of commands to execute. It also includes ‘for’ loops which define a sequence of commands to execute in order. Finally, the ‘while’ loop is given which will loop through a set of commands until some condition is met.

if

This is the basic logical control. A set of instructions is executed if a given condition is met. Note that Python decides what set of commands to execute based on how the code is indented. The ‘{’ and ‘}’ characters have a very different meaning in Python than in C, C++, or Java.

Note: See full list of control flow supported in python here

x = 5
if x > 5:
    itsBig = True
    print("x is greater than 5")
elif x < 5:
    itsBig = False
    print("x is less than 5")
else:
    itsBig = not True
    print("x is equal to 5")
print("The value of itsBig is {0}".format(itsBig))

for loops

The for statements provide the most common type of loops in Python. The idea is that a set of commands will be repeated for a fixed number of times. The command requires a variable that can be iterated over, and each time the loop repeats a new value from the variable is used. For example, if an array is given the for loop will iterate over each value within the array. (there is also a while construct).

accumulator = 0
for i in range(-10, 5, 3):
    accumulator += 1
    print(i)
print("The number of times the loop repeated is {0}".format(accumulator))

iterating over lists

It is often useful to iterate over members of lists directly

for animal in ["cat", "dog", "chinchilla"]:
    print(animal)

enumerate is also a very useful builtin when you need both an index and a list member

for i, animal in enumerate(["cat", "dog", "chinchilla"]):
    if i % 2 == 0:
        print(i, animal)

The above can be written in a single line and also save the outputs to a new list, by using list comprehension

animal_new = [animal.capitalize() for animal in ["cat", "dog", "chinchilla"]]
print(animal_new)

Careful about assignment of objects

Here we’ll creat a list from a range using list comprehension

range_list = [i for i in range(3, 7)]
print(range_list, type(range_list))

create a “new” list by assignment

x = range_list
print(x, type(x))

change an element in x

x[2] = 12
print(x)
print(range_list)

A quick exercise:

do you remember the list remember (which is a list of different types)? write a one line list comprehension to return a list of types in remember

print(remember)

Related to the for statement are the control statements break and continue. Ideally we can create a loop with logic that can avoid these but sometimes code can be more readable with judicious use of these statements. This is especially true for while loops and separate checks have to be made for iteration counts.

#  Naive prime number check

for n in range(2, 10):
    is_prime = True
    for k in range(2, n):
        if n % k == 0:
            print(n, "equals", k, "*", n / k)
            is_prime = False
            break
    if is_prime:
        print("%s is a prime number" % (n))

The while Loop

The set of commands in a while loop are executed while a given condition is True.

top = 10
bottom = 5
while top > bottom:
    print("top: {0}, bottom: {1}".format(top, bottom))
    top -= 1
    bottom += 1

The pass statement might appear fairly useless as it simply does nothing but can provide a stub to remember to come back and implement something.

def my_func(x):
    # Remember to implement this later!
    pass

Defining Functions

The last statement above defines a function in Python with an argument called x. Functions can be defined and do lots of different things, here are a few examples.

def my_print_function(x):
    print(x)


my_print_function(3)
def my_add_function(a, b):
    return a + b


my_add_function(3.0, 5.0)

A variable can be given a default value while defining the function, this value remains unchanged unless the user specifies a different value

def my_crazy_function(a, b, c=1.0):
    d = a + b**c
    return d


my_crazy_function(2.0, 3.0)
my_crazy_function(2.0, 3.0, 2.0)
my_crazy_function(2.0, 3.0, c=2.0)
def my_other_function(a, b, c=1.0):
    return a + b, a + b**c, a + b ** (3.0 / 7.0)


x, y, z = my_other_function(2.0, 3.0, c=2.0)
print(x)

Let’s try writing a bit more of a complex (and useful) function. The Fibonacci sequence is formed by adding the previous two numbers of the sequence to get the next value (starting with [0, 1]).

def fibonacci(n):
    """Return a list of the Fibonacci sequence up to n"""
    values = [0, 1]
    while (next_val := values[-1] + values[-2]) < n:
        values.append(next_val)
        print(values)


fibonacci(100)

There are several other important data structures that are useful in python including

  • tuples/sequences

  • sets

  • dictionaries

you can read more about them here

Exception Handling

Python has a very rich syntax for handling errors and exceptions which, if used sparingly can be useful when you want to fail gracefully or give the user more information about where and how a function fails.

def isstring(x):
    """function to check if x is a string

    Parameters
    ----------
    x : any python object

    Returns
    -------
    bool
        True if x is a string, False otherwise.

    Raises
    ------
    TypeError
        if x is not a string
    """
    if isinstance(x, str):
        print("{} is a string".format(x))
        return True
    else:
        raise TypeError("{} is not a string".format(x))
        return False
x = "Howdy"
isstring(x)

If you want to catch an exception and continue execution you can use the try-except syntax

print(remember)
for r in remember:
    try:
        isstring(r)
    except TypeError as err:
        print(err)

help(), ? and tab are your friends

Use the help() function or a ? at the end of a function to see the respective function’s documentation. One could also use tab key to autocomplete and view the list of available functions.

help(isstring)

A numpy example

the numpy (numerical python) module is a workhorse for numerical methods and scientific computation and provides a wealth of functions and objects

import numpy

x = numpy.array(range(3))
x
x.mean()

Coding Style

It is very important to write readable and understandable code.

This is a practical matter.

There are times when you have to go back and make changes to code you have not used in a long time. More importantly, coding is a shared activity, and if your code is not readable then it is not of any use.

Here are a few things to keep in mind while programming in and out of this class, we will work on this actively as the semester progresses as well. The standard for which Python program are written to is called PEP 8 and contains the following basic guidelines:

  • Use 4-space indentation, no tabs

  • Wrap lines that exceed 80 characters

  • Use judicious use of blank lines to separate out functions, classes, and larger blocks of contained code

  • Comment! Also, put comments on their own line when possible

  • Use docstrings (function descriptions)

  • Use spaces around operators and after commas, a = f(1, 2) + g(3, 4)

  • Name your classes and functions consistently.

    • Use CamelCase for classes

    • Use lower_case_with_underscores for functions and variables

  • When in doubt be verbose with your comments and names of variables, functions, and classes

Peer Review

To help all of us learn from each other what coding styles are easier to read we should be doing peer-reviews of the coding portions of the assignments. After the first assignment is turned in we will review a general template for code review. Please be as thorough and helpful as you can!

Example: why is this actually a poor piece of code?

def isstring(x):
    """function to check if x is a string

    Parameters
    ----------
    x : any python object

    Returns
    -------
    bool
        True if x is a string, False otherwise.

    Raises
    ------
    TypeError
        if x is not a string
    """
    if isinstance(x, str):
        print("{} is a string".format(x))
        return True
    else:
        raise TypeError("{} is not a string".format(x))
        return False

Jupyter Notebooks

We will use a lot of Jupyter notebooks in this class for both class notes (what you are looking at now) and for turning in homework. The Jupyter notebook allows for the inline inclusion of a number of different types of input, the most critical will be

  • Code (python or otherwise) and

  • Markdown which includes

    • LaTeX\LaTeX,

    • HTML

Jupyter notebooks allow us to organize and comment on our efforts together along with writing active documents that can be modified in-situ to our work. This can lead to better practice of important ideas such as reproducibility in our work.

Debugging

Debugging is one of the most critical tools we have at our disposal. Apart from standard inspection approaches (print statements) the Jupyter ecosystem has a number of ways to debug as well.

More modern versions of Jupyterlab have a built in debugger (see documentation)

For old-school Jupyter notebooks (like this one) one can access the python debugger pdb using the “magic” %pdb at the top of your notebook which will allow cause the notebook to jump into the python debugger anytime an exception is reached. This will allow you to step through your code and figure out what is wrong. If you want to step through code or just activate the trace back for the current cell use the %debug magic.

# for inline plotting in the notebook
%matplotlib inline 

# debugger
%pdb

import matplotlib.pyplot as plt
import numpy


def plot_log():
    figure, axis = plt.subplots(2, 1)
    x = numpy.linspace(1, 2, 10)
    axis.plot(x, log(x))  # <-- this line is wrong in multiple ways
    plt.show()
plot_log()  # Call the function, generate plot

Paths to debugging

  1. Check the traceback

  2. print statements

  3. Use the debugger in Jupyter Lab (>2?) or ‘debug magic %pdb’ in older versions

  4. Try a more informative IDE like PyCharm or Visual Studio Code (aka vscode)

and if all else fails

  1. Copy and paste your error message into Google to see if anyone else has experienced similar problems. You’d be surprised how often this works!

  2. Search StackOverflow

  3. Ask your favorite AI (with care)

  4. Consult fellow classmates

  5. Consult the TA’s and Professor (absolute last resort ;^)

Don’t forget to have fun...

Debugging is puzzle solving...the better you get at it, the better you can manage the frustration of numerical methods