2  Introduction to Programming (with Python)

Learning Outcomes for this Lesson

Upon completion of this lesson you should:

  • …fully accept that learning Python is very important for your success as an engineer
  • …be well versed the basic syntax of Python expressions, like the meaning of * and /
  • …know how to write simple mathematical expressions incorporating order of operations
  • …have installed Python and used the Spyder IDE to run some code snippets
  • …know how to assign values to variables and use them in subsequent expressions
  • …have used Python’s built-in functions
  • …know how to import functions from Python’s standard library
  • …be able to define your own custom functions

2.1 Why Python?

Free as in beer

The software industry is obsessed with code being “open-source”. The Free Software Foundation is one of the leading organization behind this movement if you’d like to learn more. There is an expression that “free software” means “free as in freedom” not “free as in beer”. However, that fact that all open-source software is by definition also “free as in beer” surely helps this movement. The entire modern world depends on “open-source” software. For instance, every single website is run using the Linux operating system with Apache webservers and mySQL databases, and so on.

Note that UWaterloo used to use Matlab for teaching programming, but we shifted to Python about 5 years ago. Matlab is an excellent product, but it is not open source and it is not free.

There are literally thousands of programming languages. They are called ‘languages’ because they all have their own “words” and “grammatical rules”, known as syntax. Each language was created for a specific reason, and each offers their own special features. Some reasons for choosing one language over another are:

Python has a checkmark beside nearly every box. Vanilla Python has quite slow performance but there are many ways to address this, some of which we will cover in this course.

Perhaps the main reason to learn Python right now is that it is by far the most popular general purpose programming language in the world today. This means (a) there is loads of support available, (b) a massive ecosystem of packages to add functionality (e.g. PyTorch is the leading machine learning framework and it is designed for use in Python), and (c) employers will expect you to know Python.

Figure 2.1: Programming languages sorted by popularity according to the StackOverflow annual developer survey

2.2 Motivating Examples

2.2.1 Computers Are OP calculators

What are the tools you associate most closely with the job of “engineer”? Probably a calculator!

(a) The Public Perception
(b) The Reality
Figure 2.2: “Crunching numbers” has been a main part of an engineer’s job forever. The movie Hidden Figures is an inspiring movie and a good view into how important computers are for engineering work.

Example 2.1 (Using Python as a calculator with units) If an object is travelling 1,000 furlongs every half a fortnight, what is its velocity in SI units (m/s)?

Solution

#| echo: True
import pint
u = pint.UnitRegistry()

a = 1000*u.furlongs
b = 0.5*u.fortnight
c = a/b
print(f"The velocity of the object is {c}")
c.ito_base_units()
print(f"The velocity SI units is {c}")

Comments

We will “unpack” all of the steps over the next few weeks, but hopefully you can appreciate the value of using Python for doing homework assignments in courses other that this one.

Syntax highlighting

Throughout these notes (and the internet in general) you’ll see computer programs (often referred to as “code”) written in the following format:

1# This is a comment
2a = int(1.0)
3b = 'some text'
4print(b)
1
Anything following a # is a comment and is ignored by Python
2
Numbers are given a special color so they stand out. The color scheme is called “syntax highlighting” and helps programmers recogize features of the text (i.e. numbers and text are different colors).
3
You’ll also usually see “code” written using a monospace font that looks like an antique typewriter.
4
In this code block the print() statement is actually run and the output is printed below.
some text

Also, the grey background is used to indicate a code block. And you’ll notice an icon in the top-right corner, which will “copy” the contents of the block so you can paste it into your own program.

2.2.2 Computers Are Crazy Fast

Have you ever had to do an extremely boring but repetitive task? Computers were literally invented to solve that problem!

Figure 2.3: The dream vs. the (sad) reality of writing programs that make our lives easier. One unstated takeaway from this joke is that writing the program can actually e quite fun and satisfying.
Figure 2.4: How to decide if it’s worth your time to automate a task (assuming the situation in Figure 2.3 does not occur!)

Example 2.2 (Using Python to fix mailing addresses) You have used Google Forms to collect mailing addresses of people, and now you wish to send them a letter. You realize, however, that the output of Google Forms looks like this:

Name Street and Number City Province Postal Code
Mr Jimmy Beast 100 Main St Toronto Ontario M2T1T2
Ms Taylor Swift 999 Bay St Topeka Kansas 90210

But we need them to look like the following so we can print them onto the envelopes:

Mr Jimmy Beast
100 Main St
Toronto Ontario
M2T1T2

Solution

text = "Mr Jimmy Beast, 100 Main St, Toronto  Ontario, M2T1T2"
text = text.replace(', ', '\n')
print(text)
Mr Jimmy Beast
100 Main St
Toronto  Ontario
M2T1T2

Comments

If we had a list of hundreds or thousands of addresses, the computer could zip through them all in milli-seconds (or less!).

2.2.3 Making Beautiful Data Dashboards

Many non-technical people still have to deal with data, so they will seek help from engineers (such as yourselves). There are many tools to help us make ‘user-interfaces’ so normies can interact with a computer. Even Facebook is arguably just a web-based user interface for a social network graph.

Figure 2.5: Not everyone loves data as much as engineers.

Example 2.3 (Develop a graphical user interface for simulating a hydrogen fuel cell) There are many, many frameworks available for making graphical interfaces so users can interact with a computer program. Some are extremely powerful but hard to use, while others offer basic features but are super easy. Streamlit is one of my favorites. It is very fast to learn, requires only pure Python, and renders in the web browser.

Solution

Comments

You can play with this app here.

Good news: you will be using Streamlit to make your own app for the project in this course!

2.3 Getting Started with Python

“To begin, begin”

- William Wordsworth

2.3.1 The Basic Syntax

First let’s look at mathematics, since this is one of the most common uses of Python for engineers. In “real” mathematics we use symbols like \(\times\) and \(\divsymbol\), but we do not have these on our keyboard, so we had to improvise. The following table lists the keys for each mathematical operation.

Table 2.1: List of mathematical operations and corresponding keyboard symbols (Warning: double ** is for exponentiation in Python, but if you accidentally use ^ like Excel you will not get an error but the wrong answer)
Symbol Operator Example Expression Resulting Value
- negation -5 -5
+ addition 11 + 3.1 14.1
- subtraction 5 - 19 -14
* multiplication 8.5 * 4 34.0
/ division 11 / 2 5.5
** exponentiation 2 ** 5 32
// integer division 11 // 2 5
% remainder 8.5 % 3.5 1.5
Square root sign?

What about the famous \(\sqrt 2\) sign you ask? We will have to remember our high school math rules and use 2**0.5.

Below are some snapshots of these operators in action:

11 + 3
14
11.4944 + 3.0002
14.4946
5 - 9
-4
10/2
5.0
2**16
65536
2**-0.5
0.7071067811865476

But we will do a lot more with Python that just math. Every single symbol (except 2!) on a standard QWERTY keyboard is used for something in Python. We will cover almost all of these, but below is an introductory summary:

Table 2.2: Nearly exhaustive list of characters used in Python and what they do. Don’t worry, you don’t have to remember these all at first glance.
Symbol Name Use(s)
. Period The normal decimal point, namespace demarcation
, Comma Used to separate items in a list…not like 1,000,000!
_ Underscore When used in a number it acts like a traditional comma
<, > Angled Brackets “Less than” and “greater than”
= Equal Sign Used for variable assignment…not equivalency!!
== Double Equal Used to check if two things are equivalent
!= Exclamation Mark Used to check if two things are not equivalent
* Asterisk Multiplication, arg unpacking
** Double Asterisk Exponentiation, kwarg unpacking
+ Plus Sign Addition, concatentation (joining things)
- Minus Sign Subtraction
% Percent Sign Modulus (Integer division)
/ Forward Slash Normal Division
( ) Round Brackets Indicates a tuple, encloses function arguments
[ ] Square Brackets Indicates a list, used for indexing into containers
{ } Curly Braces Indicates a dict or a set
: Colon Has quite a few uses, usually to end a statement
# Hash Tag Indicates the following text is a comment, not code
\\ Backslash Line continuation or escape character within text
@ “At” Symbol An operator for matrix multiplication (b=A@x)
~ Tilde Bitwise complement (~1001 becomes 0110)
', " Single, Double Quotes Used to denote a string of letters
& Ampersand Means and for comparing if two things are both true
| Pipe Means or for comparing if either of 2 things are true
; Semi Colon Will prevent a line from printing to terminal
? Question Mark This one is actually unused!
$ Dollar Sign This one is actually unused, but is not always present

2.3.2 Introduction to Data Types

We will introduce data types more thoroughly in the next chapter, but there is an important point to be made here: there is a difference between 1 and 1.0, and "1".

  • 1 is an integer or whole number because it has no decimal place. In Python we call this an int. We know that adding and multiplying whole numbers will result in another whole number, such as 1 + 3 = 4. However, if one of the numbers is a float then Python will convert everything in the expression to a float, like 1 + 2 + 3.0 = 6.0

  • 1.0 has a decimal so is no longer an integer. Since there is no word in English or “not an integer”, computer programmers refer to this as a “floating point number”, referring to the decimal point. In Python this is called a float. We know that division of two numbers might result in a number with a decimcal point, like 1/2 = 0.5. Python automatically converts both number to a float if the operation might return a float so that we don’t lose an information by discarding the numbers after the decimal place.

  • "1" is not even a number, it’s just the text for the number 1. This data type is called a “string” (as in string of characters) or str in Python. Obviously if we attempt to do math with the character "1" Python will not do what we want. In fact, if we attempt to multiply "1" by 2, Python will give us "11", which actually makes some sense if you think about it.

This last example is perhaps the best demonstration of why data type matters: So Python “knows” how to handle operations on each variable.

2.3.3 Operator Precedence (aka Order of Operations)

Operator precedence in Python is based upon the same concepts used in actual math:

Table 2.3: Order of operations for mathematical statements. NOTE: Operators of equal precedence are applied from left to right except exponentiation, which is applied from right to left.
Precedence Operator Operation
1 (highest) ** exponentiation
2 - negation
3 *, /, //, % multiplication, division, integer division, remainder
4 (lowest) +, - addition, subtraction

Using these precedence rules, operators of higher precedence are evaluated before those with lower precedence. In order to specific a desired order of evaluation we may use parentheses or round brackets: ().

2.3.4 Installing Python

There are many ways of installing Python. One can download the official installer, or install a code editing package that comes with Python pre-bundled (e.g. Spyder or Positron), or use the popular VSCode then add Python support though a plug-in.

For our purposes, we will do the following:

  1. Install the Anaconda distribution of Python
    • This comes with several useful features: It is bundled with hundreds of numerical-related packages, and it provides a powerful virtual environment manager (which you may find useful once you’re a Python pro)
  2. Open the “Anaconda Navigator”
  3. Launch Spyder
Figure 2.6: Screenshot of the Anaconda Navigator showing several items including Spyder

2.3.5 Using Spyder

Spyder is called an “Integrated Development Environment” or IDE. This means it is a single piece of softare (an Environment) used for doing computer programming (Development) that includes a text editor, documentation, terminal, and other helpful features all in one place (Integrated).

When we open Spyder we will see the following:

Figure 2.7: The default layout of Spyder is a text editor, help panel, and a Python consol. Each is excedingly useful and any coding environment which does not supply all three simultaneously makes things harder than necessary.
Figure 2.8: This screenshot shows Spyder with a dark color scheme, which is much easier on the eyes during long programming sessions.
Table 2.4: A sample of the more useful shortcut keys and characters in Spyder. For a full list and to customize them, go to tools/preferences/keyboard shortcuts.
Key or Character Result
F5 Runs an entire file, putting result in console
F9 Runs current line or highlighted selection
# %% Creates a new cell in a file
Shift-Enter Runs current cell
Ctrl-Enter Runs current cell and advances to the next one
Ctrl-Up Arrow Moves cursor to preceeding cell
Ctrl-Down Arrow Moves cursor to next cell
Ctrl-L Clear terminal
Alt-Up/Down Arrow Moves current line up/down
Ctrl-Alt-Up/Down Duplicates current line up/down
Tab or Ctrl-Space Open “Suggested Completions”
Ctrl-i Open documentation for selected item

Example 2.4 (Experiment with various forms of division) Paste the following code into a blank file in Spyder. Run the entire file, each cell, and each individual line.

# %%
9 - 3

# %%
8 * 2.5

# %%
9 / 2

# %%
9 / -2

# %%
9 // 2

# %%
9 % 2

# %%
9.0 % 2

# %%
9 % 2.0

# %%
9 / -2.0

# %%
4 + 3 * 5

# %%
(4 + 3) * 5

Solution

The answers will be as follows:

9 - 3
6
8 * 2.5
20.0
9 / 2
4.5
9 / -2
-4.5
9 // 2
4
9 % 2
1
9.0 % 2
1.0
9 % 2.0
1.0
9 / -2.0
-4.5
4 + 3 * 5
19
(4 + 3) * 5
35

Comments

There are few common behaviors we can identify:

  • When an int and a float are used together the result is a float. Python automatically upgrades the int to a float to prevent loss of data (i.e. digits after the decimal place).

  • Division always results in a float. This is because the result of division is quite likely to result in a decimal portion, so Python plays it safe and converts to float.

My favorite Spyder shortcut

When your cursor is in the terminal, you can press the up arrow to scroll previous commands. This is normal at most terminals. Spyder offers an extra feature: if you type a letter (i.e. p) the press the up arrow it will only show you previous commands that started with p. This is a massive time saver for repeatedly calling something like a plot command.

2.3.6 Item Assignment

So far we have used Python like a dumb calculator that just prints out an answer. It becomes a lot more useful once we store the result of each calculation for later use. For instance:

a = 9
b = 2
c = a / b
print(c)
4.5
= does not mean equal!

The main thing to note here is that = sign does not mean “equals”! It is better to think of it as gets, as in a gets 9. Or is assigned as in a is assigned 9. As we will see in Section 4.1, equality is checked with ==.

  • So if you see x = x + 5 it does not mean that the world has gone mad.
  • On the other hand if you see x == x + 5 you are correct to think the result is False.

For comparison, the language R avoids this confusion by using arrows to denote the assignment of values like:

x <- 1.0  # This does not work in Python :-(

which very clearly means that the value of 1.0 is being written to the variable x. Interestingly, this notation allows things to work in both directions too, like:

1.0 -> x  # This does not work in Python :-(

Python technically could support this notation since the -> and <- key combinations are not assigned to anything. Maybe someone will write a pep for this.

Another example of storing items in variables is for declaring constants:

1T = 298.0
# Lots of lines here
2P = n*R*T/V
1
We assign the temperature to T at the top of the file where it’s easy to find
2
And we can use T to get the pressure P. Of course we must not overwrite T before step 2!

An interesting conundrum occurs when we wish to swap values between two variables:

A = 4
B = 10
temp = A
A = B
B = temp
print("The value of A is now:", A)
print("The value of B is now:", B)
The value of A is now: 10
The value of B is now: 4

The key point is that a temporary variable must be created to store A. This is like swapping two objects on a shelf with only one hand. If you try this, you will realize that you need to put one item down in a temporary location while you move the other one. Then you can pick up the first item and finish the transfer.

2.3.7 Functions

2.3.7.1 Built-In Functions

Python includes a fairly long list of “built-in” functions, a subset of which are given in Table 2.5. Many of these will be gibberish (for now), but there are a few which we should recognize, such as abs(), round() and pow().

Table 2.5: Subset of the built-in functions in Python and what they do. For the full list see Table B.1
Function Description
abs() Returns the absolute value of a number
complex() Returns a complex number
divmod() Returns the quotient and the remainder when argument1 is divided by argument2
float() Returns a floating point number
help() Executes the built-in help system
int() Returns an integer number
pow() Returns the value of x to the power of y
print() Prints to the standard output device
round() Rounds a numbers
type() Returns the type of an object

Example 2.5 (Playing with some built-in functions)  

  1. Find the absolute value of -11.3
  2. Round pi to 3 decimal places
  3. Find the value of \(2^7\)

Solution

  1. abs(-11.3) = 11.3
  2. round(3.1141592562, 3) = 3.142
  3. pow(2, 7) = 128

Comments

There are a lot more functions available to us, but they need to be “imported”. Python only exposes the set that it deems essential. For instance, trigonometric functions are not needed by someone writing a web-scraper, while HTTP requests are not needed by an engineer computing the volume of an object.

2.3.7.2 Importing Functions

Figure 2.9: The Python Package Index, which is the main place to find new libraries has over 0.5 million packages available for free!

Python itself has a limited set of functions, like those shown in Table B.1. One of the reasons Python is especially useful are the many, many external libraries that have been written for it. In some ways, Python has benefited from the network effect, where people write libraries for Python because it has the most libraries, and so on.

Some packages are used commonly enough that Python includes them within itself. This set of extra packages is called the Standard Library. These packages contain functions which are not core to Python, but are important enough that they are officially supported by the Python team, and are available in every normal Python installation.

A prime example is the math package. It provides a huge variety of extra mathematical functions, essentially turning our standard calculator into a scientific calculator.

Installing packages

The Python Package Index is home to over 0.5 million packages. These are not included in the Standard Library so we need to install them. There are several ways to do this, but they will be discussed later in the course.

Accessing these extra powers is easy:

from math import sin, pi
sin(pi/4)
0.7071067811865475

We can also import the entire math libary and then access each of the sub-functions as follows:

import math
1math.sin(math.pi/4)
1
Note the . is used to indicate that you are accessing items below the math library. This is like a . in a section number: 1.2.a, where each indicates a new level of depth.
0.7071067811865475
Namespaces help keep things organized

When we import the entire math library using import math, we then access all the functions using math.tan(). The word math is called the namespace under which all the math functions are available. Importing the entire library prevents collisions between two functions that have the same name.

The following will cause a problem:

from math import tan
from colors import tan  # This is not a real package!

While this approach is safe:

import math, colors

# Then we access the respective tan functions as:
math.tan()
colors.tan()

There is one more useful feature of namespaces: you can choose which namespace you want to import a library as, called an alias:

import math as m

# Now we can save type 3 letters:
m.tan()

The math package has a lot of functions in it. How do you know what functions are available and what they are called? For example, what if you can’t remember if it’s called acos or arccos? Use tab/ctrl-space in Spyder to the autocompletion menu!

Figure 2.10: This screen capture not only shows the ‘autocomplete’ menu, which lists all the available functions in the math package, it also shows the help window (for the acos function) in the top right.

2.3.7.3 Custom Functions

In addition to the built-in functions, and the massive collection of extras we can install and import, we will still want to create our own custom functions. The construction of such a function requires very little extra code:

1def sqrt(x):
2    y = x**0.5
3    return y
1
There are three important things happening on this line. (1) The keyword def means define as in define a function. (2) We will call our function sqrt, which is the name we will use when we call it. (3) Inside the round brackets we list all the variables that our function requires.
2
Here we actually execute the logic of our function. This can be as many lines long as we want. Note indentation of the lines following the :! Whitespae like this crucial in Python. Refer to here: Important 2.1 for more info.
3
Finally we return the result of the calculation back to the caller.
Important 2.1: Whitespace in Python

Python is quite flexible about many things, but it is not flexible about whitespace at all!. When Guido van Rossum created Python in 1991 he had several guiding principles. One of them was the realization that “code is read more often than it is written”. For this reason he designed the syntax to use whitespace to denote logical blocks rather than brackets and braces like other languages (i.e. C/C++). The mandatory indentation makes code quite easy for a human to read since the logical blocks are instantly identifiable.

As a general rule, you should add 4 spaces each time you use a colon :, then cease adding the spaces when the block is finished.

We can use this function in our program as follows:

1def sqrt(x):
    y = x**0.5
    return y

2a = 9
3c = sqrt(x=a)
1
We have to define a function before we use it, so place the definition at the top of the script.
2
Note that the variable name a has nothing to do with the variable name x inside the function.
3
Just because we assign the result to y inside the function, does not mean that y is available outside the function. The numerical value gets returned, not the variable y. We catch the value of 3 and assign it to any variable name we like, in this case c.

Example 2.6 (Create a custom function for solving the quadratic equation) The quadratic equation occurs a lot in engineering. It arises when trying to solve equations of the form:

\[ ax^2 + bx + c = 0 \]

Write a function that accepts \(a\), \(b\) and \(c\) and returns the results. Note that there are 2 roots to the equation so the function should return 2 results.

Solution

def roots(a, b, c):
    A = 4*a*c
    B = b**2
    C = 2*a
    ans1 = (-b + (B - A)**0.5)/C
    ans2 = (-b - (B - A)**0.5)/C
    return ans1, ans2

x1, x2 = roots(5, 1, -1)
print("Result:", x1, x2)
Result: 0.35825756949558396 -0.558257569495584

Comments

This function has a few features we have not seen yet:

  • It accepts 3 values, (a, b and c). These are called “arguments”.

  • It has multiple lines of logic within it, which more clearly demonstrates the point of hiding complex logic inside a function. We only need to write one statements (x1, x2 = roots(5, 1, -1)) yet 5 lines of logic are run.

  • It returns two values, separated by a ,. We also catch the two values into separate variables simultaneously (x1 and x2).

There are a few more details about how functions behave that we we can learn from our roots function:

  • If we supply arguments only as values then Python will assume their order matches the order in the function definition. This means that roots(5, 1, 0) will make on to a=5, b=1 and c=0.

  • If we explicity name the arguments, we can put them in any order. For instance roots(c=0, a=5, b=1) will work just fine.

  • If we choose to mix both of the above, then the unnamed arguments must go first, like roots(5, c=0, b=1). In this case 5 is assumed to mean a, then c and b are assigned to their respective arguments.

As our programs get larger we may wish to “declutter” our scripts. One of the first things we can do is to move all our function definitions to a second file (and place that file in the same directory as our script). So if our main program is in a filed called main.py, we would create a second file funcs.py (or whatever you want). Then in our main.py file we would write:

1from funcs import roots

2x1, x2 = roots(5, 1, -1)
1
Here we use the from to tell Python where to look, then we import roots.
2
Once the roots function is imported into our file, we can use it as if it was defined inside the file.

Note that we could also do from funcs import * which would import all functions in funcs.py, since the * acts as a “wildcard” meaning “anything”. We could also do import funcs, then use funcs.roots() in our function.

2.4 Excercises

Copy and paste the following code to a new .py file in Spyder and work through each cell until you get the desired result.


# %% Problem 1: Basic Arithmetic Operations
# 
# Write a Python expression with one command to calculate 
# the remainder when variable A is divided by B.
# 
# Complete the code below:

A = ''
B = ''
remainder = ''

print("The remaidner is:", remainder)

# %% Problem 2: Variable Assignment - Displaying Data
#
# Assign the values "Hello" to the variable `greeting`
# and "World" to the variable `target`. Then print the 
# result in one line to be "Hello World".
# 
# Complete the code below:

greeting =''
target = ''

print('', '')

# %% Problem 3: Variable Reassignment
#
# Assign the value 10 to a variable `x`. Then, update `x`
# by multiplying it by 5 and subtracting 3. Print the 
# final value of `x`.
#
# Complete the code below:

x = ''

print("The final value of x is:", x)

# %% Problem 4: Swapping values between variables
#
# Assign 10 to x and 20 to y, then swap the values such
# that y holds 10 and x holds 20 and print the result.
# 
# Complete the code below:

x = 10
y = 20

print('The new values of x and y are:', x, 'and', y)

# %% Problem 5: Developing longer mathematical expressions
#
# Write an expression that converts a temperature to 
# Fahrenheit using the formula F = (9/5) * C + 32 and 
# print the result.
# 
# Complete the code below:

celsius = 22.0
fahrenheit = ''

print("The temperature in Fahrenheit is:", fahrenheit)

# %% Problem 6: Using the `math` library
# 
# Import the `math` library and calculate the following:
# 1. The value of 4.3 after rounding UP
# 2. The value of rounding DOWN
# 3. The logarithm (base 10) of 1000.
#
# Hint: You can use "Google" to find information
# 
# Complete the code below:

import math

rounded_up = ''
rounded_down = ''
log_value = ''

print("Ceiling of 4.3:", rounded_up)
print("Floor of 4.8:", rounded_down)
print("Logarithm (base 10) of 1000:", log_value)

# %% Problem 7: Custom functions
# 
# Define a function `nth_root` that takes a number and
# a root, and computes the nth root:
#
# Complete the code below:

def nth_root():
    pass

# Test the function
print("The 5th root of 3 is:", nth_root())
print("The square root of 8.8 is:", nth_root())

# %% Problem 8: Functions that return 2 values

# Write a function that:
# 1. Accepts the radius of a circle.
# 2. Imports the `math` module to use π.
# 3. Returns both the area and circumference of the circle.
# 
# Print the results in one line.
#
# Complete the code below:

def area_and_circumference():
    pass

# Test the function
radius = ''
area, circumference = area_and_circumference()
print("The area is:", area)
print("The circumference is:", circumference)