This the multi-page printable view of this section. Click here to print.
Lessons
- 1: 1 Course Overview, Background, and Getting Started
- 2: 2 Numbers, Strings, Variables, and Assignment Statements
- 3: 3 Boolean Comparisons, Boolean Operators, and Expressions
- 4: 4 Controlling Program Flow and Using Modules
- 5: 5 More Strings and Loops
- 6: 6 Functions, Namespaces, and Modules
- 7: 7 Tuples, Lists, and Dictionaries
- 8: 8 Files, Errors, and Exceptions
- 9: 9 Sample Project
1 - 1 Course Overview, Background, and Getting Started
Notebook 1 - Course Overview, Background, and Getting Started¶
You can make your own copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- An overview of this course
- A brief background on computing, programming, and Python
- Some getting start material
Course Overview¶
Course Description¶
Pylearn is...
- inclusive - no prerequisites, perfect for beginners
- approachable - nothing to install or configure
- convenient - learn at your pace, on your schedule
- free - no fees, no ads, not now, not ever
- practical - understand not only how, but why
- respectful - we never request or store any personal information
- interactive - learn by doing, using Colab notebooks
Course Goals¶
- Demystify programming in general and Python in particular
- Show how to use Python to a practical, hands-on way
- Provide a solid foundation for diving deeper
Target Audience¶
- Programming and/or Python beginners
- Underrepresented groups in tech
- Aspiring data scientists
There are no prerequisites for this course. We'll start at the beginning.
Syllabus¶
Lesson | Title |
---|---|
Notebook 1 | Course Overview, Background, and Getting Started |
Notebook 2 | Numbers, Strings, Variables, and Assignment Statements |
Notebook 3 | Boolean Comparisons, Boolean Operators, and Expressions |
Notebook 4 | Controlling Program Flow and Using Modules |
Notebook 5 | More Strings and Loops |
Notebook 6 | Functions, Namespaces, and Modules |
Notebook 7 | Tuples, Lists, and Dictionaries |
Notebook 8 | Files, Errors, and Exceptions |
Notebook 9 | Sample Project |
Some thoughts on learning to code¶
- Programming is a creative activity.
- There has never been a better time in history to learn to code.
- Anyone can learn to program, as long as you’re willing to do the work.
- Think of it a little like learning to play a musical instrument.
- Practice between classes is very helpful.
- Give yourself time & kindness.
- Mistakes are feedback.
- When you get stuck:
- Don’t beat yourself up.
- Take a break.
- Don’t be afraid to ask me or a peer for help.
- The dirty secret about professional programmers: No one knows everything - we all use Google, Stack Overflow, and other websites all the time.
What is a Notebook?¶
- You're looking at one. :)
- Notebooks are interactive documents where you can read instructions and try your own code experiments in one unified experience.
- The notebooks we're using are called Jupyter and the hosting service (the notebook provider) is called Google Colaboratory or just "Colab". This service makes it easy to use and share Python notebooks for free, much like Google Docs.
From the menu bar, select Tools->Command Palette to see a list of the commands you can execute inside a Colab notebook. Most of these commands are intuitively named. For example, to save your notebook in Google Drive, run "Save notebook" now from the command palette. You can find it quickly by typing "save" in the search box.
Don't worry if you're not sure what some of the commands in the command palette do. For now, I just want you to know about this resource.
Congratulations for making it this far! Your last assignment is to watch this short video and take a quick tour of Colab.
It's a good idea to save your work after a notebook session. You can use the command palette to do this, as you just did in the previous cell, but since this is a common function, you'll see an easier way to save your notebook via the File->Save menu item.
When you run a cell in a notebook, where does it actually run?¶
Background¶
The March of Progress¶
The cell phone in your pocket has more computing power than all of NASA back in 1969. They used it to put two astronauts on the moon. We use it to play Candy Crush. :)
Learning to program gives you the power to build amazing things with your computer.
What is an operating system?¶
A software layer that functions as the computer’s traffic cop.
Operating systems provide:
- core system services (e.g. processes/threads, memory management)
- abstraction of lower level services (e.g. reading a file, using the network)
- permissions and access control (e.g. login authentication, verifying a user is allowed to access a file)
- resource management (e.g. multiple programs sharing the CPU, multiple access to the same disk device)
Popular operating systems: Windows, Linux, Android, iOS, MacOS
What is an algorithm?¶
A step-by-step procedure for solving a problem or accomplishing some end, especially by a computer
Example Algorithm: Marc’s Scrambled Eggs Recipe
Ingredients:
- 2 eggs
- butter
Cooking steps:
- mix eggs in a bowl
- lightly coat a frying pan with gasoline
- heat pan to 4000 degrees
- stir eggs until firm
- pour mixed eggs into frying pan
Just like programs, recipes can have errors, or "bugs". Can you find three bugs in this recipe?
What is a program?¶
- Encoding of an algorithm, in some particular programming language.
- In other words, a set of instructions, which tell a computer how to do something.
- Programming statements are called “source code” or just “code”.
- "Coding" is just another word for programming.
What is a programming language?¶
- A set of rules for expressing algorithms symbolically.
- Provides an abstraction layer for using your computer to solve a problem.
- Provides a way to reuse other peoples’ work.
Compiled vs. interpreted languages¶
[Source](https://medium.com/from-the-scratch/stop-it-there-are-no-compiled-and-interpreted-languages-512f84756664)- A compiler converts one language to another (usually a high level language to low level code that can be run directly by the hardware).
- An interpreter executes the source program one statement at a time.
- Portability vs. performance
What is Python and why learn it?¶
- an interpreted programming language that was invented in 1989 by Dutch programmer Guido van Rossum
- powerful and expressive
- easy to learn
- highly readable
- freely available open source
- widely used & well supported
- Very strong for:
- AI
- Data Science
- Finance
- Statistics
- Machine Learning
- Social Sciences
- Web development
- Education
- not a bad thing to have on your resume/CV
The Zen of Python¶
import this
Getting Started¶
Running a Python program from the command line¶
Example Programs¶
Example 1 - Print a Multiplication Table¶
for i in range(1,10):
print(i, end=' ')
for j in range(2, 10):
print(f'{i*j:2}', end=' ')
print()
Example 2 - Search IMDb¶
!pip install -q imdbpy
from imdb import IMDb
from IPython.display import Image
title = 'trainspotting'
imdb = IMDb() # create imdb API object
results = imdb.search_movie(title)
limit = 3
count = 0
for i in results:
if count > limit:
break
count += 1
if i.data[‘kind’] == ‘movie’:
movie = imdb.get_movie(i.movieID)
imdb.update(movie, info=[‘vote details’])
if ‘rating’ in movie.data:
display(Image(url=movie.data[‘cover url’]))
print(movie, movie.data[‘rating’])
Example 3 - Generate a Histogram¶
import seaborn as sns
countries = [‘UK’, ‘UK’, ‘India’, ‘UK’, ‘India’, ‘UK’, ‘Sweden’,
‘India’, ‘India’, ‘India’, ‘India’, ‘UK’, ‘India’, ‘Nigeria’,
‘US’, ‘Afghanistan’, ‘Afghanistan’, ‘US’, ‘UK’, ‘US’,
‘Afghanistan’, ‘UK’]
sns.countplot(countries)
Example 4 - Generative AI built into Colab¶
Try this in Colab:
# prompt: generate a colorful time series graph, make the values sparse, i.e. fewer than 20
import matplotlib.pyplot as plt
import numpy as np
import random
# Generate sparse time series data
num_points = random.randint(10, 19) # Ensure fewer than 20 data points
time_values = np.linspace(0, 10, num_points)
data_values = np.random.randint(1, 50, num_points)
# Create a colorful time series plot
plt.figure(figsize=(10, 6))
plt.plot(time_values, data_values, marker=‘o’, linestyle=’-’, color=‘blue’, linewidth=2)
plt.fill_between(time_values, data_values, color=‘skyblue’, alpha=0.4)
plt.xlabel(‘Time’)
plt.ylabel(‘Values’)
plt.title(‘Colorful Time Series Graph (Sparse Data)’)
# Add some aesthetics
plt.grid(True, linestyle=’–’, alpha=0.7)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
# Show the plot
plt.show()
Your first Python program¶
print('Hello world!')
Just for context, here's how we wrote that program in 1982:
HELLO CSECT The name of this program is 'HELLO'
* Register 15 points here on entry from OPSYS or caller.
STM 14,12,12(13) Save registers 14,15, and 0 thru 12 in caller's Save area
LR 12,15 Set up base register with program's entry point address
USING HELLO,12 Tell assembler which register we are using for pgm. base
LA 15,SAVE Now Point at our own save area
ST 15,8(13) Set forward chain
ST 13,4(15) Set back chain
LR 13,15 Set R13 to address of new save area
* -end of housekeeping (similar for most programs) -
WTO 'Hello World' Write To Operator (Operating System macro)
*
L 13,4(13) restore address to caller-provided save area
XC 8(4,13),8(13) Clear forward chain
LM 14,12,12(13) Restore registers as on entry
DROP 12 The opposite of 'USING'
SR 15,15 Set register 15 to 0 so that the return code (R15) is Zero
BR 14 Return to caller
*
SAVE DS 18F Define 18 fullwords to save calling program registers
END HELLO This is the end of the program
Now you see why I love Python. :)
The print
function provides a mechanism to generate output from a Python program. Another useful function to know about is the input
function. It's sort of the opposite of print
, because it gathers input into your program. Here's an example:
print('Enter your name: ')
name = input()
print('Hello', name + '!')
Printing a prompt before gathering input is such a common pattern that you can combine them into one function call, like this:
name = input('Enter your name: ')
print('Hello', name + '!')
🎉 Congratulations - you are now a Python programmer! 🎉
Errors - when bad things happen to good programs¶
"42" + "1"
Three kinds of errors¶
- syntax errors: invalid Python
print'Hello from Python')
- runtime errors: legal code tries to do something illegal
primt('Hello from Python')
print(10 / 0)
- logic errors: code is legal and runs fine but it does the wrong thing
age = birth_year - current_year
Comments¶
#
marks the rest of the line as a "comment"- Ignored by Python
- useful for documenting your code
- blank lines are also fine and sometimes improve readability
Example:
# Python ignores this line.
# print('this line does not print anything when commented out')
# The following blank lines are ignored as well...
#print(‘hi’) # this is a comment
# I can have as many comments and blank lines as I like in a program.
# They are for the benefit of me and “future me”.
Try removing the # preceding the print function call in the previous cell. We call this "uncommenting" a line of code.
Now put the # back in place. We call this "commenting out" a line of code. Often we'll do this to temporarily disable some code from running, where we want to keep the code in place for possible future use.
What is a function?¶
A reusable piece of code that completes a specific task.
We just met two functions - print
and input
are functions you used to do input/output (I/O) operations.
We say "call" or "invoke" a function to request that it do its job.
We do this by writing the function name followed by parentheses (aka brackets).
We may optionally include some values inside the brackets. We call those values function arguments, or just arguments.
We'll often refer to this process as "passing arguments" to a function.
I like to think of the function as a work request and the arguments as the job specification.
For example, you can pass arguments to the print function to produce just about any desired output.
print('test')
print()
print('this', 'is', 'a', 'test')
Built-in functions, like print
and input
are always available to use.
More about print¶
A print function call with no arguments simply prints a blank line:
print()
# Now try calling the print function with something else, like your name...
print('your name here')
Functions are the basic building blocks of a Python program. Later we'll see how to define your own functions. We've seen a simple print function call with zero and one argument:
print('My name is Marc')
We can also print a sequence of arguments, where spaces are added between each element in sequence, like this:
print('My', 'name', 'is', 'Marc')
print('next line')
By default, we get a newline character at the end of a print request but we can override that behavior using the end
argument:
print('My name is Marc')
print('next line')
print(‘My name is Marc’, end=’\n’)
print(‘next line’)
We can also change the separator string, using the sep
argument, like this:
print('My', 'name', 'is', 'Marc', sep='\n')
We could even use a null (empty) separator:
print('My', 'name', 'is', 'Marc', sep='', end='')
print('hi')
Useful Python Resources¶
Official Resources
Books and Articles
- A Whirlwind Tour of Python
- My Favorite Books for Beginning Python Students
- Spreading the Gospel of Python
- Why learn Python?
- codingbat.com
- CodeKata
Learning Resources
IDEs
Recommended Book for Further Study¶
Automate the Boring Stuff with Python
Great fit for our course:
- well written
- free to read online
- perfect for beginners
- focusses on practical applications
- if you buy it, make sure you get the 3rd edition
Documentation¶
- Official Python Documentation
- the
help
function (see example below) - Stack Overflow
- AI - I like Github Co-Pilot and Google Gemini
help(print)
2 - 2 Numbers, Strings, Variables, and Assignment Statements
Notebook 2 - Numbers, Strings, Variables, and Assignment Statements¶
You should make your own copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- The basic data types you can work with in Python
- What are variables and why they are useful
- How to create and assign values to variables in Python
Data Types¶
Python knows about several types of data. Three common ones are:
- numeric types
- integers
- floating point numbers
- character strings
Numeric Types¶
Python supports two main types of numbers
- int, arbitrary size signed integers, like these:
2024
-999999999999
- float, arbitrary precision floating point numbers, like these:
3.14159
3.8 * 10**6
For the most part, you don't need to worry about which type of number to use - Python will take care of that for you. The decimal point tells Python which to use.
Numerical/Arithmetic Operators¶
operator | operation on numbers | operation on strings |
---|
+|addition|concatenation -|subtraction|undefined *|multiplication|repetition //|integer division|undefined /|floating point division|undefined %|modulus (remainder)|undefined **|exponentiation|undefined ()|prioritization|prioritization
Mixing floats and ints results in a float so, for example, 2024 * 3.14
results in a floating point number.
Try entering these expressions in the following cell:
print(5 - 6)
print(8 * 9)
print(6 // 2)
print(6 / 2)
print(5.0 / 2)
print(5 % 2)
print(2 * 10 + 3)
print(2 * (10 + 3))
print(2 ** 4)
Were there any outputs you didn't expect?
print(5 - 6)
print(8 * 9)
print(6 // 2)
print(6 / 2)
print(5.0 / 2)
print(5 % 2)
print(2 * 10 + 3)
print(2 * (10 + 3))
print(2 ** 4)
Strings¶
Rules of the Road¶
- a character string (or just "a string") is a sequence of characters surrounded by quotes
- strings give you the ability to operate on a sequence of characters as a basic unit
- strings are an example of a general category of data types called sequences, which we'll see more of later
- you can use single or double quotes to delineate strings but you must be consistent within a string
"this works"
and'this works'
too"this is not ok'
,'nor is this"
Embedded Quotes¶
How to embed a single or double quote inside a string?
You can embed a single quote in a double quoted string:
"this string's fine"
'but this doesn't work'
You can embed a double quote in a single quoted string:
'but this string is "A-OK"'
"but this "example" fails"
You can also "escape" a single or double quote with the backslash character:
'Marc\'s string'
"I like \"The Office\""
Escape Sequences¶
These are useful for embedding special characters in a string.
character | escape sequence |
---|---|
tab | \t |
newline | \n |
single quote | \' |
double quote | \" |
backslash | \\ |
Examples are shown in the next cell...
# escape sequence examples
print(“Quoth the raven: "nevermore".")
print(‘My country 'tis of thee’)
print(‘line1\nline2\nline3’)
print(“field1\tfield2\tfield3”)
print(‘c:\directory\file’)
print(‘several escape sequences:\n1/1\t1/2\n2/1\t2/2’)
Triple Quoted Strings¶
Sometimes you want to define a long string, that spans multiple lines, or a string containing embedded single and/or double quotes and don't want to use a lot of escape characters. Triple quoted strings are perfect for this job:
longstr = """
This is a triple quoted string.
It goes on for multiple lines.
It contains things like ' and ",
which would normally have to be escaped,
but anything goes in a triple quoted string.
"""
print(longstr)
Challenge¶
For each of the following examples tell me if it’s a legal Python string...
'Go ahead, make my day.'
"There's no place like home."
'Frankly my dear, I don't give a damn'
'Say "hello" to my little friend'
"You\'re gonna need a bigger boat"
'"You talkin' to me?"'
'Fasten your seatbelts, it\'s going to be a bumpy night'
'''Striker: "Surely you can't be serious!" Rumack: "I am serious... and don't call me Shirley".'''
"You had me at "hello"."
String Operators¶
+
and *
can be used to operate on strings.
Try the code in the cell below. One of these raises an error. What do you think the error message means?
print("Cat")
print("Cat", "videos")
print("Cat" + 3)
# To notice: 33 (the integer) is different from "33" (the string)
print(33 + 33)
print(type(33+33))
print(“33” + “33”)
print(type(“33” + “33”))
add * operator
Converting to a string¶
You can use the str()
function to convert a numeric type into a string, like this:
print("Cat" + "3")
print("Cat" + str(3))
String Methods¶
Method: A reusable piece of code that that is connected to a value or a variable
We invoke a method using the
.
syntax, e.g.mystr.upper()
.Methods are specific to a data-type e.g.
.upper()
can be used with a string but not an integer or a float
print("Cat".upper())
print("Cat".lower())
print("the lord of the rings".title())
Challenge¶
- you have 5 cats
- each cats consumes two tins of food per day
- how many tins needed per week
In the next cell, create a program that calculates how many FIX. You will need:
- A
print()
function call to output the result
Extension: change the calculation to work out the amount needed for 7 days.
Variables¶
Variables are names - a reusable label for a data value
name = 'Marc'
print(name)
name = 3.14
print(name)
Variable Naming Rules¶
There are some special rules governing variable names:
- variable names may start with a letter or an underscore
- the rest of the letters in the name may be letters, numbers or underscore case is significant
name
is different fromNAME
, which is different fromNaMe
Challenge¶
Which of the following are legal Python variable names?
average = 1
Max = 1
print = 1
LadyGaGa = 1
_Lady_Ga_Ga = 1
FiftyYardLine = 1
50_yard_line = 1
yard_line_50 = 1
raise = 1
my-cat-is-awesome = 1
my_cat_is_awesome = 1
Reserved Words¶
The following words have special meaning in Python. We call them keywords or reserved words and you may not use these names for your program variables.
and, as, assert, async, await, break, class, continue, def, del, elif, else, except, False, finally, for, from, global, if, import, in, is, lambda, nonlocal, None, not, or, pass, raise, return, True, try, while, with, yield
Constants vs. Variables¶
Literal values (like
"marc"
and2024
) are called constants because their value is fixed, unlike variables, whose associated value may change (or vary) over time.The data a variable refers to may be simple, e.g. a number or a string, or it may be complex, e.g. a list or an object (we'll learn about those later).
customer_id = "1234"
account_balance = 1000
print(f'customer id: {customer_id} account balance: {account_balance * 2}')
F-strings¶
We often need to combine variables, values, and strings. For example, if we have the following variables:
customer_id
account_balance
we might want to print a report, where each line summarizes the values above. We could do that like this:
print("customer id: ", customer_id, ", account balance:", account_balance)
which produces this output:
customer id: 123 , account balance: 17.9
This sort of construct gets a bit tedious. Plus the space between the customer id and the following comma is unintended and undesirable.
A relatively new addition to Python, called f-strings, offer a simpler and more readable solution to this problem. If you prefix a string with the character f
, it gives the string magic powers. Specifically, the sting has the ability to interpolate variables inside curly braces. Here's how we could express the previous print
statement using an f-string:
print(f"customer id: {customer_id} account balance: {account_balance}")
This is shorter, less tedious, easier to read and write, and solves the formatting issue related to the comma between the two fields.
Note that you can put any Python code inside the curly braces, so this technique is very powerful. Once you get going with Python, you'll find all sort of wonderful ways to use f-strings.
Assignment Statements¶
Assignment statements are used to associate a variable name with some simple or complex value general form:
variable_name = 'some_value'
If a variable doesn’t already exist, when you assign to it, Python creates it on the fly.
If you assign to a variable that already exists, Python replaces its current value with a new value.
Examples¶
instructor = 'marc' # string value
instructor = 'my evil twin' # same name, diff string value
instructor = 42 # same name, integer value
todays_high_temp = 71.3 # diff name, floating point value
Try some experiments in the next cell...
a = 0
print('a =', a)
a = 42
print('a =', a)
a = 3.14159
print('a =', a)
a = "Marc"
print('a =', a)
b = 42
print(‘a =’, a, ‘b =’, b)
a = b
print(‘a =’, a, ‘b =’, b)
b = 43
print(‘a =’, a, ‘b =’, b)
Usage Example¶
Let's print a list of the first 9 multiples of 9...
# Tedious version...
print(19)
print(29)
print(39)
print(49)
print(59)
print(69)
print(79)
print(89)
print(9*9)
# Nicer version...
factor = 8
print(1factor)
print(2factor)
print(3factor)
print(4factor)
print(5factor)
print(6factor)
print(7factor)
print(8factor)
print(9*factor)
Why is that nicer? Think about changing the requirement to print multiples of eight, instead of nine. How many lines would you have to change in the firat example? How about the second example?
The second example uses a redirection via a variable to make the code much easier to update. This is a very powerful and important concept in software engineering.
# Coming soon...
factor = 8
for i in range(1, 9):
print(i * factor)
Key Points¶
- Basic data types in Python include integers, floating-point numbers, and strings (there are others but we'll start with these).
- Use
variable = value
to assign a value to a variable in order to store it in memory. - Variables are created on demand whenever values are assigned to them for the first time.
Challenges¶
Problem 1¶
Prompt for user's name and print it back with "Nice to meet you, name!".
#@title Double click here to reveal solution
name = input (‘What is your name? ‘)
print (‘Nice to meet you,’, name +’!’)
Problem 2¶
Prompt for dog's age and return in people years in this format:
X dog years = Y people years.
#@title Double click here to reveal solution
dog_to_people_years = 7
dog_age = input(‘How old is your dog? ‘)
people_age = int(dog_age) * dog_to_people_years
print(dog_age,‘dog years =’, str(people_age), ‘people years.’)
Problem 3¶
Write a program to calculate the total number of hours in a fixed set of days, where the number of days is assigned to a variable.
#@title Double click here to reveal solution
days = 365
hours = 24
total_hours = days * hours
msg = f‘There are {total_hours} hours in {days} days.’
print(msg)
Problem 4¶
Write some Python code that uses all the maths operators you've learned.
#@title Double click here to reveal solution
a = 2 + 3
print(a)
a = 3 - 2
print(a)
a = 2 3
print(a)
a = 2 / 3
print(a)
a = 2 // 3
print(a)
a = 2 % 3
print(a)
a = 2 ** 3
print(a)
a = (2 + 3) 4
print(a)
Problem 5¶
In one statement, calculate and print your weekly pay if you earn $100 a day in a five day work week.
Store your daily pay rate in a variable and use the variable to calculate and print your weekly pay.
Congratulations - you just got a raise. Your new daily pay rate is $120. Assign a new value to the daily pay variable, then calculate and print your new weekly pay.
Store the number of days in a week in a variable, then calculate and print your weekly pay.
Calculate and print your weekly, monthly, and annual pay using only variables. Assume 5 workdays per week, 20 workdays per month and 240 workdays per year.
Print the your first, middle, and last name, each on a separate line.
Print the your first, middle, and last name, all on one line.
3 - 3 Boolean Comparisons, Boolean Operators, and Expressions
Notebook 3 - Boolean Comparisons, Boolean Operators, and Expressions¶
You can make your own copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- More about types in Python
- Comparison operators
- Boolean operators
- How to combine values, variables, and operators into expressions
More Data Types¶
In Python, values have a type. We already saw three data types in the previous lesson (ints, floats, and strings). In this lesson we'll learn about some additional types, how to determine a variable's type, how to convert a value from one type to another, and how to combine values, variables, and operators into complex expressions.
The Boolean (bool
) Type¶
Python supports a special type called booleans, written bool
in Python, which are used to indicate whether something is true or false. Booleans have one of two possible values:
True
False
When evaluating a number as a boolean, the following rules apply:
- 0 is
False
- 0.0 is
False
- all other numerical values are
True
When evaluating a string as a boolean, the following rules apply:
- the empty string (
""
and''
) isFalse
- all other strings are
True
Marc’s Law of Boolean Evaluation in Python: “nothingness is false, somethingness is true”
The None Type¶
Python has a special type called None
and it means no value.
It's a good choice when you want to initialize a variable without an obvious choice for the initial value, like this:
name = None
None always evaluates to False in boolean expressions.
Type Conversion and the type
Function¶
Type Conversion Functions¶
These are built-in Python functions to convert from one type to another. The name of the function mirrors the type you want to convert to, and you pass a variable or value to be converted.
int(x)
- converts x to an integerfloat(x)
- converts x to a floating point numberstr(x)
- converts x to a stringbool(x)
- converts x to a boolean value
When do you need to convert a type?¶
When you have an expression containing mixed types...
name + number
age * 365
When you call a function that requires a different type than you are passing. For example, the len
function returns the length of a string so it expects the value passed to it will be a string.
print(len(42))
won’t work, but...
print(len(str(42)))
is fine. Try both of those experiments in the code cell below.
Here's an example where type conversion is needed:
birth_year = input("Enter the year you were born:")
age = 2024 - birth_year
print(f"You are {age} years old")
Try running this code in the following cell and, when it fails, see if you can fix it.
birth_year = input("Enter the year you were born:")
age = 2024 - birth_year
print(f"You are {age} years old")
The type() function¶
The built-in type
function returns the type of the passed value. It conveys the type name in a string of the form <class type-name>
. Don't worry too much for now about that format - just know that the name after class
is the type name.
>>> type(123)
<class 'int'>
Run the next cell to see the type
function in action.
type_int = type(123)
type_float = type(3.14)
type_str = type("a string")
type_bool = type(True)
type_none = type(None)
print(type_int, type_float, type_str, type_bool, type_none)
s = input("enter your age: ")
print(type(s))
Comparison Operators¶
As their name suggests, comparison operators allow us to compare values and result in a boolean type indicating whether the comparison is True
or False
.
The following table summarizes the most commonly used operators in Python, along with their definition when applied to numbers and strings.
operator | operation on numbers | operation on strings |
---|---|---|
== | equal to | equal to |
!= | not equal to | not equal to |
> | greater than | lexicographically greater than |
>= | greater than or equal to | lexocographically greater than or equal to |
< | less than | lexicographically less than |
<= | less than or equal to | lexocographically less than or equal to |
Challenge¶
Which boolean value (True
or False
) do each of these expressions evaluate to?
123 == 10
10 == 123
123 == 123
123 != 321
123 != 123
age == 64
"andrew" == "marc"
"marc" == "andrew"
"marc" == "marc"
"marc" != "Marc"
>
and >=
¶
123 > 10
10 > 123
123 > 123
123 >= 123
age >= 65
age > min_age
"fred" > "marc"
"marc" > "fred"
"marc" >= "marc"
"marc" > "Marc"
Boolean Operators - and
, or
, and not
¶
Boolean operators are special operators in Python that let you combine boolean values in logical ways corresponding to how we combine truth values in the real world. An example of a boolean and expression would be "I'll buy a new phone if I like the features and the price is low". There are three main boolean operators: and
, or
, and not
. We'll look at examples of each in the next cells.
Boolean and
¶
A and B
is True
only true when both A and B are True
, otherwise it's False
.
Example:
- I ride my bike only when it's both sunny and warm.
- In other words, if
is_sunny
andis_warm
are bothTrue
then I will ride my bike.
In Python...
if is_sunny and is_warm:
print("ride bike")
We haven't learn about if
statements so don't worry if the previous construct looks unfamiliar. It's a simple way of checking the value of a boolean expression, but we'll dive deeper into if
statements in our next lesson.
is_sunny = False
is_warm = False
print(is_sunny and is_warm)
Truth Table for boolean and
¶
var1 |
var2 |
var1 and var2 |
---|---|---|
False |
False |
False |
True |
False |
False |
False |
True |
False |
True |
True |
True |
Boolean or
¶
A or B
is True
when either A or B are True
, or when both are True
, otherwise it's False
.
Example:
- I ride my bike when it's sunny, warm, or both.
- In other words, if
is_sunny
oris_warm
(or both) areTrue
then I will ride my bike.
In Python...
if is_sunny or is_warm:
print("ride bike")
Truth Table for boolean or
¶
var1 |
var2 |
var1 or var2 |
---|---|---|
False |
False |
False |
True |
False |
True |
False |
True |
True |
True |
True |
True |
Truth Table for boolean not
¶
var1 |
not var1 |
---|---|
False |
True |
True |
False |
is_rainy = False
is_warm = True
if not is_rainy and is_warm:
print(“ride bike”)
else:
print(“don’t ride today”)
Expressions¶
- Python lets us combine values, variables, and operators into larger units called expressions.
- When evaluating an expression, Python internally replaces the variable names with the values to which they refer.
- Expressions appear in many places, for example:
- numerical calculations
2 + 2
- assignment statements
age = age + 1 # we do this every birthday
- function calls
print(age * 365) # number of days alive
- numerical calculations
- As we learn more, we'll see expressions popping up all over the place.
Challenge¶
Let's combine input
, print
, and expressions to build a temperature conversion program. The formulae for converting Celcius to Fahrenheit and vice versa are:
F = (C + 32) * 9/5
C = (F - 32) * 5/9
degrees_f = float(input('Enter number of degrees in fahrenheit: '))
# Add an assignment statement that computes degrees_c as a function of degrees_f
print(f'{degrees_f}°F = {degrees_c}°C')
degrees_c = input('Enter number of degrees celsius: ')
# Add an assignment statement that computes degrees_f as a function of degrees_c
print(f'{degrees_c}°C= {degrees_f}°F')
Types of Expressions¶
- arithmetic expressions
tax = income * tax_rate
- comparative expressions
error_count <= 0
- boolean expressions
more_to_do and no_errors
- combinations of the above
valid_ticket and (cur_day - purchase_day) < exp_window
Order of Evaluation¶
How does Python know the correct order to evaluate a complex expression?
Example: 4 + 1 * 5
Is that (4 + 1) * 5
, which is 25
?
Or is it 4 + (1 * 5)
, which is 9
?
Another example: True or False and False
Is that (True or False) and False
, which is False
?
Or is it True or (False and False)
, which is True
?
Python uses operator precedence rules to avoid this ambiguity and evaluate expressions in a predictable way.
Simplified Python Precedence Rules¶
This is a subset of the complete rules (in order of highest to lowest precedence):
- parentheses (innermost to outermost, left to right)
- exponentiation (left to right)
- multiplication, division, modulus (left to right)
- addition, subtraction (left to right)
- comparisons (left to right)
- boolean not
- boolean and
- boolean or
Practical Advice¶
Marc's rule or operator precedence: When in doubt, use parentheses.
I make liberal use of parentheses because then:
- I don't need to remember the precedence rules.
- I don't have to worry about surprises.
- It makes my code more readable.
For example, I could write this expression, which evaluates A and B
first, then C and D
, and finally takes the boolean or
of the two preceding results:
A and B or C and D
but I much prefer to make this explicit so I don't have to think about precedence rules every time I look at this code:
(A and B) or (C and D)
Challenges¶
Evaluate the following expressions mentally, then verify your answers in the following code cell...
2 + 3 * 4 + 5
(2 + 3) * (4 + 5)
1 * 2 + 3 * 4 + 5 * 6
100 / 25 / 2
(100 / 33) <= 3
(100 // 33) <= 3
True and False and True
True and True or False and False
100 % 99
(100 / 100) // (100 % 100)
print(2 + 3 * 4 + 5)
print((2 + 3) * (4 + 5))
print(1 * 2 + 3 * 4 + 5 * 6)
print(100 / 25 / 2)
print((100 / 33) <= 3)
print((100 // 33) <= 3)
print(True and False and True)
print(True and True or False and False)
print(100 % 99)
print((100 / 100) // (100 % 100))
Assuming we have the following variables and functions:
customer_id
- unique identifier per customercustomer_age
- customer age in yearsactive_marketing_campaign
-True
if we're allowed to start a new marketing campaign andFalse
otherwisepurchased(customer_id)
- returnsTrue
if this customer already bought the product andFalse
otherwise
Run the following cell to initialize three variables.
customer_id = 987
customer_age = 49
active_marketing_campaign = True
Your marketing department wants to start a new campaign. Formulate an expression to determine whether it's ok to start a new marketing campaign.
Marketing says you're allowed to offer the discount to adults only. Formulate an expression to determine whether it's ok to start a marketing campaign and the customer is 18 years of age or older.
Marketing says you're bothering too many people with this promotion. Refine the previous boolean expression by excluding people who've already purchased the product.
Write a print
statement to verify the previous expression results in the expected type using a built-in function.
Assume you have the following variables:
birth_year
- year customer was born in a stringcur_year
- current year in a string
Formulate an arithmetic expression to calculate and print the customer's current age.
4 - 4 Controlling Program Flow and Using Modules
Notebook 4 - Controlling Program Flow and Using Modules¶
You can make your own copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- how to control program flow based based on conditional values
- how to represent blocks of code
- how to express complex conditional logic
- how to import and use modules in your programs
Controlling Program Flow¶
Trivia Question: Will the year 2100 be a leap year?
- Up to now, we've looked at very simple programs, involving a sequence of statements (A, then B, then C…)
- But real world algorithms are rarely so simple
Example: If a year is divisible by 4, then it's a leap year, otherwise it's not a leap year.
This is called conditional logic because the program’s logic or execution path is determined by the testing of a true or false condition.
Everyday example: if it's not raining then I'll ride my bike to work, otherwise I'll take public transit.
if
Statements¶
- The
if
statement is how we express conditional logic in Python. - Virtually every programming language has this concept.
- If statements define a condition and a sequence of statements to execute if the condition is
True
.
Prototype...
if some_expression:
do_this()
do_that()
If the condition is true, the indented statements are executed.
Otherwise, the indented statements are skipped and program execution continues after the if
statement.
month = "jan"
summer_months = ["may", "jun", "jul", "aug"]
if month in summer_months:
print(“yay - it’s still summer!")
else:
print(“boo - have to go to school today!")
print(“program done”)
Challenge¶
In Python, we use indentation to associate a block of statements with a condition, for example...
print("1")
if some_condition:
print("2")
print("3")
print("4") # this line is NOT part of the if block
What does the output look like...
- when
some_condition
is True? - when
some_condition
is False?
Try testing your guess by running the following cell with some_condition
set to True, then False.
some_condition = True
print(“1”)
if some_condition:
print(“2”)
print(“3”)
print(“4”) # this line is NOT part of the if block
Here’s a slightly different example...
print("1")
if some_condition:
print("2")
print("3") # this line is NOT part of if block
print("4") # this line is NOT part of if block
What’s different? What does the output look like...
- when the
some_condition
is True? - when the
some_condition
is False?
if
Block Structure¶
- In Python,
if
statements blocks are defined by indentation. - This idea of using indentation to delineate program structure is pervasive in Python and unique across programming languages.
- For now, we're focusing on if statements but later we'll see how indentation is used to define other blocks of statements.
Block Stucture in Other Languages¶
In other languages, explicit delineators are used. For example, in Java, C, and C++ we would write:
if ((cur_year % 4) == 0) {
leap_year = true;
}
whereas, in Python we write:
if (cur_year % 4) == 0:
leap_year = True
Indentation in Java/C/C++ is a helpful practice for program readability but it does not affect program functionality. In Python, indentation is not just a good idea - it's affects program logic!
Python's use of whitespace¶
- Many people have strong opinions about this aspect of Python.
- Personally, I find that it leads to compact, readable code.
- My advice: don’t get hung up on this feature. Try it and see what you think after you've written a few Python programs.
- Pitfalls:
- watch out for mismatched indentation within a block
- avoid mixing tabs and spaces in your code
- I prefer spaces over tabs because it's more explicit.
Marc's law of whitespace: Pick spaces or tabs and be consistent.
Encoding our leap year rule¶
Remember our leap year rule?
- if the current year is divisible by 4, we have a leap year
Here's a short Python program, which prompts the user to enter a year and uses the strategy above to decide if the year entered was (or will be, if in the future) a leap year:
leap_year = False # initialization
year = input("Enter desired year: ")
year = int(year)
if (year % 4) == 0:
leap_year = True
print(f"leap year status for {year}: {str(leap_year)}")
Question: Why is the first statement needed?
leap_year = False # initialization
year = input("Enter desired year: ")
year = int(year)
if (year % 4) == 0:
leap_year = True
print(f"leap year status for {year}: {str(leap_year)}")
else
Statements¶
Sometimes we want to specify an alternative to the if
condition, which we do with an else
statement, for example...
if <condition>:
<block1>
else:
<block2>
- If the condition is true, block1 is executed.
- if the condition is false, block2 is executed.
The else cause is Python's way of saying "otherwise..."
Just as if
blocks are defined by indentation, else
blocks are also defined by indentation.
For example, this:
if <condition>:
<statement1>
else:
<statement2>
<statement3>
is different from this:
if <condition>:
<statement1>
else:
<statement2>
<statement3>
Challenge¶
Consider the following if/else statement...
print("1")
if some_condition:
print("2")
else:
print("3")
print("4")
Question: what does the output look like…
- when the
some_condition
is True? - when the
some_condition
is False?
Try testing your guess by running the following cell with some_condition
set to True, then False.
some_condition = False
print(“1”)
if some_condition:
print(“2”)
else:
print(“3”)
print(“4”)
Let's use if
and else
to express our leap year rule¶
Can we use if/else to rewrite our leap year program to avoid needing this initialization step:
leap_year = False # initialization
year = input("Enter current year: ")
year = int(year)
if (year % 4) == 0:
leap_year = True
else:
leap_year = False
print(f"leap year status for {year}: {leap_year}")
elif
Statements¶
Sometimes we need one or more intermediate conditions between the if and else parts, for example...
if A then do X, else if B then do Y, otherwise do Z
We use the elif
statement to express this in Python...
if condition1:
do_thing_1()
elif condition2:
do_thing_2()
else:
do_thing_3()
- If
condition1
is true,do_thing_1()
is executed. - Else, if
condition2
is true,do_thing_2()
is executed. - Otherwise,
do_thing_3()
is executed.
elif
blocks are defined the same way asif
andelse
blocks - using indentation.It's good to have an if/elif for every condition of interest and not lump errors together with cases of interest.
For example, if you care about values 1 and 2 and everything else is considered an error, this code:
if month == "jan": # deal with jan here
apply_jan_discount()
elif month == "feb": # deal with feb here
apply_feb_discount()
else: # deal with errors here
apply_no_discount()
is better than this:
if month == "jan":
apply_jan_discount()
else: # x must be 2 then, right? not necessarily!
apply_feb_discount()
The latter code hides errors by combining a valid case with error cases.
Challenge¶
Consider the following if/elif/else statement...
print("1")
if condition1:
print("2")
elif condition2:
print("3")
else:
print("4")
print("5")
What does the output look like...
- when both conditions are False?
- when both conditions are True?
- when
condition1
isTrue
andcondition2
isFalse
? - when
condition1
isFalse
andcondition2
isTrue
?
Try testing your guess by running the following cell with condition1
and condition2
set to the four possible combinations of True and False.
condition1 = False
condition2 = True
print(“1”)
if condition1:
print(“2”)
elif condition2:
print(“3”)
else:
print(“4”)
print(“5”)
Nested if
Statements¶
- Sometimes we need to embed one if statement inside another.
- We call these nested if statements.
- We can embed if statements inside any part of an
if
statement, i.e. in theif
block or theelif
block or theelse
block. - We can embed
if
statements arbitrarily deeply, i.e. we could have, for example...
if condition1:
if condition2:
if condition3:
...
Challenge¶
print("1")
if condition1:
print("2")
if condition2:
print("3")
else:
print("4")
else:
print("5")
print("6")
What does the output look like...
- when both conditions are False?
- when both conditions are True?
- when
condition1
isTrue
andcondition2
isFalse
? - when
condition1
isFalse
andcondition2
isTrue
?
Try testing your guess by running the following cell with condition1
and condition2
set to the four possible combinations of True and False.
condition1 = False
condition2 = True
print(“1”)
if condition1:
print(“2”)
if condition2:
print(“3”)
else:
print(“4”)
else:
print(“5”)
print(“6”)
Improving Our Rule¶
A more accurate rule for determining leap years:
If the current year is divisible by 4 but not divisible by 100 then we have a leap year. How would you encode that refinement in Python? Try it yourself and then have a look at the next cell.
leap_year = False
year = input("Enter current year: ")
year = int(year)
if (year % 4) == 0:
if (year % 100) != 0:
leap_year = True
print(f"leap year status for {year}: {leap_year}")
Variation - using boolean logic instead of nested if
¶
This example could also be coded using boolean logic in place of the nested if statement.
leap_year = False
year = input("Enter current year: ")
year = int(year)
if (year % 4) == 0 and (year % 100) != 0:
leap_year = True
print(f"leap year status for {year}: {leap_year}")
What does the above code print for these years...
- 2020?
- 2100? -- that answers our trivia questions from the beginning of this notebook
- 2000? -- surprise, that one's wrong!
The Whole Story About Leap Years¶
A year is a leap year if its divisible by 4, but not divisible by 100, unless also divisible by 400.
So...
- 2008 was a leap year because it's divisible by 4 and not divisible by 100.
- 2100 will NOT be a leap year because although it's divisible by 4, it is also divisible by 100.
- 2000 was a leap year because although it's divisible by 4 and 100, it's also divisible by 400!
Importing Modules¶
import
is how you use someone else's code.
Let's say we want to generate a random number between 1 and 100. We can use the Python random
module, like this...
import random
good_dogs = [“Benji”, “Maple”, “Kirby”, “Rosie”]
for i in range(3):
random.shuffle(good_dogs)
print(good_dogs)
Challenges¶
Question 1¶
Write a program that prompts the user for their birth year and prints their age in years, days, hours, minutes, and seconds. Don’t worry about the user’s birthday (just subtract the birth year from the current year to get the approximate current age in years) and you can ignore leap years. Here's a sample run:
Enter your birth year: 1970
You are...
50 years old
18250 days old
438000 hours old
26280000 minutes old
1576800000 seconds old
# Add your code here
Extra challenge: write the program so that it works any year it is run, not just this year (hint: google the Python datetime
module, which you can use to find out the current year).
# Add your code here
#@title Double click here to reveal solution
Coming soon.
Question 2¶
A person is eligible to be a US Senator if they are at least 30 years old and have been a US citizen for at least 9 years.
A person is eligible to be a US Representative if they are at least 25 years old and have been a US citizen for at least 7 years.
Write a program to prompt a user to enter their age and length of citizenship in years, then print out one of the following three statements:
- You are eligible to run for both the House and Senate.
- You eligible to run only for the House.
- You are ineligible to run for either branch of Congress.
Something to think about: why don't we also have the possibility of printing "You are eligible to run only for the Senate"?
# Add your code here
#@title Double click here to reveal solution
Coming soon.
Todo¶
In last class, show how to test this program using functions (turn this snippet into a function), loops, boolean logic, lists, and pytest.
Question 3¶
Write a program that generates two random 1 digit integers and prompts the user to provide the product of those two digits (basically, this is an interactive multiplication test). Check the user's answer and print a response indicating whether it is correct or not. Here are two sample runs:
Welcome to the multiplication tester!
What is 3 * 9? 25
Sorry, that's incorrect, 3 * 9 = 27.
Welcome to the multiplication tester!
What is 4 * 7? 28
Correct!
# Add your code here
#@title Double click here to reveal solution
Coming soon.
Question 4¶
Write a program that prompts the user for a year and, using the complete set of rules described in class, tells the user whether the provided year is a leap year or not.
# Add your code here
#@title Double click here to reveal solution
Coming soon.
5 - 5 More Strings and Loops
Notebook 5 - More Strings and Loops¶
You can make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- more about strings
- while loops and for loops
- break and continue statements
Strings Revisited¶
The len
Function¶
len(string)
returns the length of a passed string
Examples:
print(len('To be or not to be'))
print(len('To be or not to be\n'))
print(len('Tobeornottobe'))
print(len(''))
print(len('To be or not to be'))
print(len('To be or not to be\n'))
print(len('Tobeornottobe'))
print(len(''))
Indexing¶
You can refer to a particular character in a string with this syntax:
string[offset]
Indexing works with variables and literal strings.
In Python, offsets in strings always start counting at zero.
Examples:
mystr = 'marc'
print(mystr[0])
print(mystr[1])
print(mystr[4])
print(mystr[len(mystr)-1])
print('marc'[2])
mystr = 'marc'
print(mystr[0])
print(mystr[1])
print(mystr[4])
print(mystr[len(mystr)-1])
print('marc'[2])
Slicing¶
You can take a subset of a string, we call this 'a slice') using this syntax:
string[beg:end]
the slice starts from offset
beg
(again, counting from 0), and continues through toend-1
Examples:
mystr = 'Python is cool'
print(mystr[0:6])
print(mystr[10:11])
print(mystr[10:10])
print(mystr[10:9])
print('Python is cool'[1:3])
mystr = 'Python is cool'
print(mystr[0:6])
print(mystr[10:11])
print(mystr[10:10])
print(mystr[10:9])
print('Python is cool'[1:3])
ssn = '123-456-7890'
code = input('give me the last four digits of your ssn: ')
if code == ssn[8:]:
print('you are in!!')
else:
print('sorry!')
Default Limits¶
- You can leave out the beg or end part (or both)
- default beg is
0
- default end is
len()-1
Examples:
mystr = 'Python is cool'
print(mystr[0:])
print(mystr[10:])
print(mystr[:14])
print(mystr[:7])
print(mystr[:])
mystr = 'Python is cool'
print(mystr[0:])
print(mystr[10:])
print(mystr[:14])
print(mystr[:7])
print(mystr[:])
Challenge - predict the output from the following print statements:¶
mystr = 'Python is cool'
print(mystr[7])
print(mystr[len(mystr)])
print(mystr[1:15])
print(mystr[1:1])
print(mystr[-5:-1])
print(mystr[-1:-5])
print(mystr[0:])
print(mystr[:-1])
mystr = 'Python is cool'
print(mystr[7])
print(len(mystr))
print(mystr[len(mystr)-1])
print(mystr[1:15])
print(mystr[1:1])
print(mystr[-5:-1])
print(mystr[-1:-5])
print(mystr[0:])
print(mystr[:-1])
The in
Operator¶
in
is a boolean operator which, when used with strings, tests whether a substring exists in another string- the result is a boolean value (True or False)
Examples:
print('a' in 'Marc')
print('arc' in 'Marc')
print('m' in 'Marc')
print('Marc' in 'Marc')
print('' in 'Marc')
print('a' in 'Marc')
print('arc' in 'Marc')
print('m' in 'Marc')
print('Marc' in 'Marc')
print('' in 'Marc')
String Methods¶
- everything in Python is an object
- strings (and other objects) have special functions attached to them which we call "methods"
- string methods are invoked like this:
string.method()
- you can use a variable, like this:
name.capitalize()
or a string literal, like this:'marc'.capitalize()
name = 'Marc Cohen'
print(len(name))
print(name.upper())
print(name.lower())
find()
returns the offset of the first matching substringfind()
returns -1 if no match is found
Example:
mystr = 'bookkeeper'
print(mystr.find('ookkee'))
print(mystr.find('okay'))
mystr = 'bookkeeper'
print(mystr.find('ookkee'))
print(mystr.find('okay'))
print(mystr.find('k'))
print(mystr.find(''))
String Immutability¶
- strings cannot change after they are created
- we call this property “immutability”
- strings are said to be “immutable” (i.e. unchanging)
- string methods which appear to change the string actually return a new string
For example, the upper()
string method:
name = 'Marc'
upper_name = name.upper()
print(f'name={name} still exists, upper_name={upper_name} is a new string')
creates and returns a new string. The original string, the one we called the method on, returns a new string.
name = 'Marc'
upper_name = name.upper()
print(f'name={name} still exists, upper_name={upper_name} is a new string')
String Editing¶
the replace() string method takes these arguments:
- a substring to replace
- a replacement string
- an optional maximum number of occurrences to replace (default is all)
Example: replace every zero with two zeros...
salary = '1000'
new_salary = salary.replace('0', '00')
print(new_salary)
salary = '1000'
new_salary = salary.replace('0', '00')
print(new_salary)
Example: improve your reviews...
feedback = input('How am I doing? ')
better_feedback = feedback.replace('worst', 'best')
print(better_feedback)
feedback = input('How am I doing? ')
improved_feedback = feedback.replace('worst', 'best')
print(improved_feedback)
Loops¶
Stop repeating yourself¶
Let's write a program that prints the first three integers along with the square of each (the number multiplied by itself).
i = 1
print(i, i*i)
i = 2
print(i, i*i)
i = 3
print(i, i*i)
i = 1
print(i, i*i)
i = 2
print(i, i*i)
i = 3
print(i, i*i)
Two problems with this program¶
scalability - what happens to this program if I change the specification from the first three integers to the first 1,000 integers?
You need to type 2,000 more lines of code.
maintainability - what happens to this program if I change the specification to require the cube of each number?
You need to edit every single print statement. After expanding this program to cover the first 1,000 integers, you'd have to change 1,000 lines!
while
Loops¶
- The
while
statement is one way we can express repeatable logic in Python. while
statements define a condition and a sequence of statements in this format...
while condition:
block of indented statements
...
print('done with loop')
- The condition is checked on entry to the loop and after each execution of the loop block.
- Similar to
if
statement except the block is repeatedly executed as long as the condition is true. - When the condition becomes false, the loop is exited, i.e. it stops repeating and control moves to the statement after the loop.
- If the condition is initially false, the loop is never entered, i.e. the loop block is never executed, not even once.
while
Loop Structure¶
In Python, the extent of a while block is defined by indentation (just like if statement blocks)
i = 0
while i < 3:
print(i)
i = i + 1
print(i)
What does the output look like...
- when
i
is initialized to0
? - when
i
is initialized to1
? - when
i
is initialized to2
? - when
i
is initialized to3
? - when
i
is initialized to4
?
i = 0
while i < 3:
print(i)
i = i + 1
print(i)
Loop Counter¶
- variable that controls entry into the loop body
- often keeps track of the number of times the loop is executed
- must be initialized
- must be changed at some point in the loop block to ensure the loop is executed the intended number of times
Assignment Operators¶
i = i + i
can be written like this:i += 1
- More generally,
i = i + x
can be written asi += x
, which adds the value of x to i and assigns the result toi
. - You'll often see this syntax inside loops when we want, for example, to increment a loop counter every time through the loop body.
- We have similar assignment operators available corresponding to the other arithmetic operators:
operator | equivalent |
---|---|
x += y | x = x + y |
x -= y | x = x - y |
x *= y | x = x * y |
x /= y | x = x / y |
x **= y | x = x ** y |
x %= y | x = x % y |
Generating Squares Using a while
Loop¶
i = 1
while i <= 3:
print(i, i*i)
i += 1
- Does this work?
- Is this version an improvement? Why?
i = 1
while i <= 3:
print(i, i*i)
i += 1
Scalability¶
- Can you make this work for the first 1,000 integers?
i = 1
while i <= 1000: # changed 3 to 1000
print(i, i*i)
i += 1
- We scaled this version to 1,000 integers by changing only one line of code!
i = 1
while i <= 1000: # changed 3 to 1000
print(i, i*i)
i += 1
Maintainability¶
- Can you print both the square and the cube (3rd power) for the first 1,000 integers?
i = 1
while i <= 1000:
print(i, i*i, i**3) # added cube to print
i += 1
- The previous version would have required us to edit 1,000 print statements.
- We added support for printing cubes in this version by changing only one line of code!
i = 1
while i <= 1000:
print(i, i**2, i**3) # added cube to print
i += 1
Another Problem¶
- Notice that the loop limit is embedded in the while condition.
- It would be nice if this "configuration data" were separate from the program logic.
- It would also be nice if we could change the limit dynamically without having to change the code, for example, as a function of user input.
A Better Version¶
limit = 10
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
- By using a variable, we can set the limit separately from the loop condition.
limit = 10
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
An Even Better Version¶
limit = int(input('desired number of integers: '))
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
- By using a variable, we can set the limit separately from the loop condition.
- We can also set the limit dynamically, by gathering input from the end user.
limit = int(input('desired number of integers: '))
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
Challenge - Find the Bugs¶
There are (at least) four bugs in this program. Can you find them?
start = input('desired starting integer: ")
input('desired ending integer: ')
while i < end:
print(i, i*i, i**3)
start = input('desired starting integer: ")
input('desired ending integer: ')
while i < end:
print(i, i*i, i**3)
Nested while
Loops¶
Just as we had if statements of if statements, we can have while loops of while loops (and so on...)
i = 1
while i <= 3:
j = 1
while j <= 3:
print(i, '/', j, end='\t')
j += 1
print()
i += 1
Result:
1 / 1 1 / 2 1 / 3
2 / 1 2 / 2 2 / 3
3 / 1 3 / 2 3 / 3
i = 1
while i <= 3:
j = 1
while j <= 3:
print(i, '/', j, end='\t')
j += 1
print()
i += 1
Infinite Loops¶
- An infinite loop is a loop that never ends.
- This is usually not by design (!).
Does this loop end?
i = 1
while i <= 3:
print(i, i*i, i**3)
Challenge: how about this one...
i = 5
while i != 0:
print(i, i*i, i**3)
i -= 2 # same as i = i - 2
i = 5
while i != 0:
print(i, i*i, i**3)
i -= 2 # same as i = i - 2
Debugging Infinite Loops¶
- inserting print statements to see what's going on
- we call this "tracing code"
- when problem is fixed, you can comment out or delete your tracing code
Example:
# calculate sum of first n integers
i = 1
n = 10
sum = 0
while i <= n:
i += 1
#print(f"adding i={i} to sum={sum}")
sum += i
print('sum of 1 to', n, '=', sum)
i = 1
while i < 100:
print("I'm running forever")
# calculate sum of first n integers
i = 1
n = 10
sum = 0
while i <= n:
i += 1
#print(f"adding i={i} to sum={sum}")
sum += i
print('sum of 1 to', n, '=', sum)
This progam is fail-safe, right?¶
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment: '))
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
Is there any way this program can go into an infinite loop?
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment: '))
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
Trusting User Input¶
- Getting input from external sources adds flexibility because program behavior can be changed at runtime (without requiring you to change your code).
- The price of that flexibility is that you have to worry about the consequences of receiving bad input.
A More Robust Version¶
import sys # needed for exit() function
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment (>0): '))
if (incr <= 0):
sys.exit('incr must be positive')
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
- you should also verify start and end are numeric
- you can do that with the string method isnumeric()
break
Statement¶
- The break statement exits a loop immediately.
- It's often used to exit a loop under certain circumstances.
- It's Python's way of saying "get me out of here!"
break
Example¶
- Our last version catches bad input but is very unforgiving - it exits as soon as it gets bad input.
- This version gives the user more chances to get it right...
while True:
incr = int(input('increment (>0): '))
if incr > 0:
break
print('bad input, increment must be positive')
print('off we go...')
while True:
value = int(input('positive value (>0): '))
if value > 0:
break
print('bad input, value must be positive')
print('off we go...')
continue
Statement¶
- The continue statement jumps to the top of a loop immediately.
- The loop condition is retested and the next iteration begun (if true) or the loop is exited (if false).
- It's Python's way of saying "let's take it from the top".
continue
Example¶
- Assume we want
start
andend
to be positive. - If the
start
value is bad, don't bother prompting forend
.
start = 0
end = 0
while (start <= 0) or (end <= 0):
if start <= 0:
start = int(input('starting integer (>0): '))
if (start <= 0):
continue
if end <= 0:
end = int(input('ending integer (>0): '))
if end <= 0:
continue
print(‘using start’, start, ‘and end’, end)
start = 0
end = 0
while (start <= 0) or (end <= 0):
if start <= 0:
start = int(input('starting integer (>0): '))
if (start <= 0):
continue
if end <= 0:
end = int(input('ending integer (>0): '))
if end <= 0:
continue
print(f"using start {start} and end {end}")
for
Loops¶
while
loops repeat program logic based on a condition.for
loops repeat program logic based on the contents of a sequence .- We say they "iterate over a sequence".
For example, this program prints the characters in a string, one character per line:
mystr = 'this is a test'
for i in mystr:
print(i)
Result:
t
h
i
s
...
for i in 'Python':
print(i)
Numerical for
Loop¶
- For loops can also be used with a sequence of numbers, similar to while loops.
- For example, this program prints the first three integers and their squares...
for i in (1, 2, 3):
print(i, i*i)
Result:
1 1
2 4
3 9
- Notice a big advantage over while loops: there's no need to separately initialize and increment a loop counter.
- But enumerating the range of numbers is cumbersome.
for i in (1,2,3, 4, 5, 6, 7, 8, 9, 10):
print(i, i*i)
The range()
Function¶
- the Python
range()
function generates a sequence of numbers - prototype: range([start], end, [increment])
- start is optional and defaults to zero
- end is required and terminates the sequence at end-1
- increment dictates the interval between elements
- increment is optional and the defaults to one
range()
returns a special Python type called a "range"
for i in range(0, 10):
print(i, i*i)
range
works great in for
loops¶
Print the print first 20 integers and their squares
for i in range(1, 21):
print(i, i*i)
for i in range(1, 21):
print(i, i*i)
Nested for
Loop¶
- Just as we have seen
if
statements ofif
statements, andwhile
loops ofwhile
loops, we can havefor
loops offor
loops (and so on...)
Example: print a 9-by-9 multiplication table
print('X', end='\t')
for i in range(1, 10):
print(i, end='\t') # column headings
print()
for i in range(1, 10):
print(i, end='\t') # row headings
for j in range(1, 10):
print(i * j, end='\t') # products
# print newline at end of each row
print()
print('X', end='\t')
for i in range(2, 10):
print(i, end='\t') # column headings
print()
for i in range(2, 10):
print(i, end='\t') # row headings
for j in range(2, 10):
print(i * j, end='\t') # products
# print newline at end of each row
print()
Example Program - Image Manipulation¶
from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode
def take_photo(filename=‘photo.jpg’, quality=0.8):
js = Javascript(’’’
async function takePhoto(quality) {
const div = document.createElement(‘div’);
const capture = document.createElement(‘button’);
capture.textContent = ‘Capture’;
div.appendChild(capture);
const video = document.createElement(‘video’);
video.style.display = ‘block’;
const stream = await navigator.mediaDevices.getUserMedia({video: true});
document.body.appendChild(div);
div.appendChild(video);
video.srcObject = stream;
await video.play();
// Resize the output to fit the video element.
google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);
// Wait for Capture to be clicked.
await new Promise((resolve) => capture.onclick = resolve);
const canvas = document.createElement(‘canvas’);
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext(‘2d’).drawImage(video, 0, 0);
stream.getVideoTracks()[0].stop();
div.remove();
return canvas.toDataURL(‘image/jpeg’, quality);
}
‘’’)
display(js)
data = eval_js(‘takePhoto({})’.format(quality))
binary = b64decode(data.split(’,’)[1])
with open(filename, ‘wb’) as f:
f.write(binary)
return filename
from IPython.display import Image
try:
file = take_photo()
print(f"Image saved to {file}")
image = Image(file)
# Show the image which was just taken.
display(image)
except Exception as err:
# Errors will be thrown if the user does not have a webcam or if they do not
# grant the page permission to access it.
print(str(err))
import matplotlib.pyplot as plt
I = plt.imread('photo.jpg')
I = I.sum(axis=-1)
plt.figure()
plt.imshow(I, cmap="gray")
iterations = 10
for x in range(iterations):
for i in range(1,I.shape[0]-1):
for j in range(1,I.shape[1]-1):
I[i,j] = (
I[i-1,j-1] + I[i-1,j] + I[i-1,j+1] +
I[i,j-1] + I[i,j] + I[i,j+1] +
I[i+1,j-1] + I[i+1,j] + I[i+1,j+1]
) / 9.0
plt.figure()
plt.imshow(I, cmap="gray")
Challenges¶
Question 1¶
- Using a while loop, repeatedly prompt the user for an input string, and print the input string.
- Exit the loop when the user enters only the character ‘q’ or an empty string.
Enter a string (‘q’ to quit): this is a test
You entered: this is a test
Enter a string (‘q’ to quit): another test
You entered: another test
Enter a string (‘q’ to quit): q
Exiting...
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
print(f“You entered: {value}")
Question 2¶
Building on the previous program, for each string entered, print the string as entered, followed by an all upper case version and an all lower case version, like this:
Enter a string (‘q’ to quit): I Am The Walrus
Original string: I Am The Walrus
Upper case: I AM THE WALRUS
Lower case: i am the walrus
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
print(f“Original string: {value}")
print(f“Upper case: {value.upper()}")
print(f“Lower case: {value.lower()}")
Question 3¶
Building on the previous program, for each string entered, print whether the entered string is all upper, all lower or mixed case, like this:
Enter a string (‘q’ to quit): this is a test
You entered: this is a test, which is lower case
Enter a string (‘q’ to quit): ANOTHER TEST
You entered: ANOTHER TEST, which is upper case
Enter a string (‘q’ to quit): mIxEd CaSe
You entered: mIxEd CaSe, which is mixed case
Hints:
- Use a for loop to examine the characters in the string.
- Use isupper() and islower() string methods to examine the case of each character in the loop.
- What can you infer by counting the upper and lower case characters in a given string?
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
upper_cnt = 0
lower_cnt = 0
length = 0
case = None
for c in value:
if c.isspace():
continue
length += 1
if c.isupper():
upper_cnt += 1
elif c.islower():
lower_cnt += 1
if upper_cnt == length:
case = “upper”
elif lower_cnt == length:
case = “lower”
else:
case = “mixed”
print(f“You entered: {value}, which is {case} case”)
Question 4¶
- The factorial of a number (written x!) is the product of 1, 2, ..., x.
- In other words, it's the result of multiplying 1, 2, ..., all the way up to (and including) the number x itself.
- Prompt the user for a number and print a table of factorials from 1 up to the entered number.
- For example, if the user inputs 5, the output should look something like this (with table headings):
x x!
1 1
2 2
3 6
4 24
5 120
Don't worry if your columns don't line up perfectly.
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a number (‘q’ to quit): “)
if not value or value == ‘q’:
break
value = int(value)
fact = 1
print()
for i in range(1, value + 1):
fact *= i
print(f"{i}\t{fact}")
print()
Question 5¶
- Repeatedly prompt the user for a string (until receiving ‘q’ or an empty string).
- For each string received, use a for loop to create a reversed version of the string.
- As you step through the characters in the string, successively build a reversed version by prepending each char to the reversed version of the string (i.e.
revstr = char + revstr
) - Print the reversed string along with an indication as to whether or not it's a palindrome, written the same forwards and backwards, like 'racecar'. * Don't worry about dealing with upper/lower case issues. You can assume all input is lower case.
- Also don't worry about doing anything special with whitespace characters (assume "race car" is not a palindrome).
Enter a string: hello
hello/olleh - is NOT a palindrome
Enter a string: redivider
redivider/redivider - is a palindrome!
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a number (‘q’ to quit): “)
if not value or value == ‘q’:
break
revstr = ”"
for c in value:
revstr = c + revstr
if value == revstr:
print(f"{value}/{revstr} - is a palindrome!")
else:
print(f"{value}/{revstr} - is NOT a palindrome”)
print()
Just For Fun - Some Palindromes¶
Character-wise
- A man, a plan, a canal, Panama!
- I prefer pi
- Dammit, I'm mad!
- Ma is as selfless as I am
- Never odd or even
- Madam, I'm Adam
Word-wise
- King, are you glad you are king?
- Says Mom, "What do you do?" – You do what Mom says.
- You know, I did little for you, for little did I know you.
- Please me by standing by me please.
- Blessed are they that believe they are blessed.
- Escher, drawing hands, drew hands drawing Escher.
6 - 6 Functions, Namespaces, and Modules
Notebook 6 - Functions, Namespaces, and Modules¶
You can make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- what functions are and why they useful
- how to define and use functions
- the global and local namespaces
- what modules are and how to use them
- Integrated Developer Environments (IDEs) for Python
Functions¶
Functions are flexible software building blocks¶
- So far, we’ve been writing small programs.
- Things get much more complicated when we write large programs, especially with multiple authors.
- Ideally, we'd like to build software like snapping lego pieces together.
- What would that buy us?
- abstraction
- reuse
- modularity
- maintainability
Abstraction - You don't need to do everything¶
- When building a house, you don't do everything yourself.
- You hire an architect, a carpenter, an electrician, a roofer, a plumber, a mason.
- You might hire a contractor to hire and manage all those people.
- In our programs we delegate tasks to certain functions, like
print()
, so that we don't have to worry about all the details.- It's a bit like hiring an electrician so that we don't have to worry about the details of electrical wiring in our house.
Reuse - Don't Reinvent the Wheel¶
- it's ok to reuse other people's work
- it's not stealing
- it makes you more efficient and more productive
- Very few people build a house from scratch
- so don't try to build programs from scratch
Reuse Example¶
You can count the number of words in a string the hard way...
mystr = 'This is a test.'
cnt = 0
for i in mystr:
if i.isspace():
cnt += 1
cnt += 1
print('number of words =', cnt)
Or the easy way, by calling a string method...
mystr = 'This is a test.'
cnt = len(mystr.split())
print('number of words =', cnt)
Which would you rather use? Which is more reliable?
- The first approach is great for learning.
- The second approach is great for getting real work done.
Modularity - Divide and Conquer¶
- So far, our programs have been monoliths - one continous sequence of Python statements.
- Real programs are often much bigger than the ones we've written.
- Google's software repository has billions of lines of source code (Why Google Stores Billions of Lines of Code in a Single Repository)
- No one person can write a program that big.
- Large programs are built by teams.
- In order to build large, complex programs, we need the ability to divide program logic into manageable pieces.
- We call this modularity - dividing software into pieces or modules.
Maintainability - Keeping your code DRY (Don't Repeat Yourself)¶
- Imagine that you need to do roughly the same thing in ten different places so you copy the code to those ten locations.
- What happens when you find a bug or want to improve that piece of code?
- You need to make the change ten times.
- Will you remember to do that?
- If you do remember, will you catch all ten locations?
- Copying code is a bad thing - it leads to bugs.
Functions solve all of these Problems¶
- Functions give us the ability to:
- Hide low level details (abstraction)
- Share and reuse pieces of functionality (reuse)
- Split programs into manageable pieces (modularity)
- Write one copy of an algorithm and use it anywhere (maintainability)
- We've already used several functions
print()
,input()
,int()
,len()
,range()
, etc.
- Now let's see how to define our own functions.
Defining Functions¶
- example:
def function_name(arg1, arg2):
'''This is a docstring.''' # optional but a good idea
statement1
statement2
...
- Not surprisingly, we define the scope of the function body using indentation (just like how we define blocks for if statements, for loops, etc.).
- This is a bit like an assignment statement in that it assigns a block of code (the function body) to the function name.
- Function names have the same rules as variable names.
- This only defines a function - it doesn't execute it.
def speak():
'''
This function generates hello in cat language.
Don't try this near a dog.
'''
print('meow')
Notice how nothing seemed to happen in that last cell. But something did - we defined a function. However, that function doesn't get executed unless and until we call it. Let's do that now.
speak()
Docstrings¶
- a string defined immediately after the
def
line - usually triple quoted since it may be multi-line
- not required but a good way to document your functions
- IDEs use the docstring to make your life easier
- automates output of
help(function)
Example Function¶
# The factorial of N is defined as 1*2*...*N.
# Here's an example function definition which prints the factorial of 10.
def fact10():
'''
Print the factorial of 10.
Factorial of 10 is defined as 1*2*...*10.
'''
limit = 10
result = 1
for i in range(1, limit+1):
result *= i # result = result * i
print(result)
# Get help about this function...
help(fact10)
# And here's how we would call this function...
fact10()
Passing Values to a Function¶
fact10()
is limited.- It only prints the factorial for one value (10).
- It would be nice if we could define a more flexible version, that could print the factorial of any number, like this...
def fact(num):
'''Print the factorial of any number.'''
result = 1
for i in range(1, num+1):
result *= i
print(result)
fact(20)
Parameters and Arguments¶
- The variables we define in a function to take on the values passed by the caller are called parameters.
- In this code,
a
,b
andc
are parameters:
def sum(a, b, c):
return a + b + c
- The values supplied by the caller when calling a function are called arguments.
- In this code,
1
,2
, and3
are arguments:
sum(1, 2, 3)
Passing Arguments¶
- Functions can define any number of parameters, including zero.
- Multiple parameters are separated by commas, like this...
def product(a, b, c):
return a * b * c
- If you pass the wrong number of arguments, you'll hear about it:
product(1, 2)
...
TypeError: product() takes exactly 3 positional argument (2 given)
...
Return Values¶
Instead of printing the result, we can also have the function return a result to the caller so that the caller can print it or use it in a calculation.
def fact(num):
''' compute factorial of any number '''
fact = 1
for i in range(1, num+1):
fact *= i
return fact
Here's how we would call this function:
f = fact(5)
print('5! = ', f)
- Functions return a value to the caller via the
return
statement. - The
return
statement causes two things to happen...- the function ends and program execution resumes just after the place where the function was called
- the returned value is passed back to the caller
- You can have as many return statements as you like (including zero).
- If the caller wants to do something with a returned value, it needs to save it or use it in an expression...
resp = input('Yes or No (y/n)? ')
- If the caller ignores the return value, it's lost...
input('Yes or No (y/n)? ') # this loses the answer
Returning Multiple Values¶
- Functions can return multiple values.
- Simply specify more than one comma separated value in the
return
statement. - This function returns two values...
def divide(dividend, divisor):
quotient = int(dividend / divisor)
remainder = dividend % divisor
return quotient, remainder
- You could receive the results like this:
(q, r) = divide(100, 9)
print(f'quotient={q}, remainder={r}')
def divide(dividend, divisor):
quotient = int(dividend / divisor)
remainder = dividend % divisor
return quotient, remainder
(q, r) = divide(100, 9)
print(f“quotient={q}, remainder={r}")
Challenge¶
Can you think of a function we've used which...
- takes no arguments?
- takes only one argument?
- takes a variable number of arguments?
- returns no return values?
- returns one return value?
- returns a sequence of values?
#@title Double click here to reveal answers...
# takes no arguments?
print()
# takes only one argument?
input(‘Enter your name: ‘)
# takes a variable number of arguments?
print(‘hello’)
print(‘hello’, ‘world’)
# returns no return values?
print()
# returns one return value?
len(‘test’)
# returns a sequence of values?
range(5)
Namespaces¶
- A
namespace
is an abstract collection of variables that exist in a particular context.- Kind of like a company's ID numbers.
- My id is only valid within the scope of my company.
- We've seen an example of a namespace in this code...
import random
x = random.randrange(10)
- In this case, the random module has it's own namespace, which is completely separate from the rest of your program and is accessed via "dot" notation.
- If you neglect the "random." prefix, you're referring to a different namespace: your program's global namespace.
Global Namespace¶
- Every Python source file has its own global name space.
- The global namespace includes all names (variables and functions) defined outside of any functions, in the Python source file.
- In the following code,
count
,mystr
andreq
are all global variables, i.e. they all reside in the global namespace.
count = 0
while True:
mystr = input('? ')
if (mystr == '1'):
req = 'add'
else:
break
count += 1
Local Namespace¶
- When you define a function, Python creates a local namespace for that function, encompassing all parameters and variables created inside the function body.
- Variables in a local namespace are separate from, and independent of, variables in the global name space.
- Inside a function, local variables supersede global variables with the same name.
- Local variables are transient - they exist only during the lifetime of function execution.
Example illustrating local variable superseding a global variable¶
var = 1
def myfunc():
var = 2 # local scope
print('local var:', var)
myfunc()
print('global var:', var)
Example illustrating the transient nature of the local namespace¶
def func():
var2 = 2 # local scope
print('local var:', var2)
func()
print('global var:', var2)
Modules¶
- A module is a file of reusable Python code.
- The module's name is the file without the .py extension.
- We've already seen how modules are imported...
import random
- In order for Python to see one your modules, the module file (with .py extension) needs to be in the same folder as the program you are running or in a special, predefined system location.
sys.path
in PythonPYTHONPATH
in shell environment
Module Namespace¶
- Every module has its own namespace, which is independent of the main source file's global namespace
- You can access objects in a module's namespace using this general syntax:
module-name.variable
module-name.function(<args>)
- For example, as we've already seen...
rand_val = random.randrange(0, 10)
import random
print(random.random())
print(random.randint(1, 10))
The from
Statement¶
- You can also import code using this syntax...
from module-name import *
- This says loads all the names (*) from the designated module into the global namespace.
- With this kind of import, the module names get loaded into the global namespace, which means you don't need to qualify your accesses with the
module-name.
prefix. - For example, you could do this...
from random import *
rand_val = randrange(0, 10)
# I didn't need to use random.randrange(0, 10)
- You can also import selected names from a module
from random import randrange, randint
- This says load only those names explicitly listed (
randrange
andrandint
, in this case) from the designated module into the global namespace. - As in the previous example, after this import the names are loaded into the common global namespace so there is no need to qualify them...
from random import randrange, randint
rand_val = randrange(0, 10)
from random import random, randint
print(random())
print(randint(1, 10))
When to use import
vs. from
¶
- Generally, it's better to use
import
because...- less risk of name clashes and other surprises
- makes your code more explicit and clear
- Occasionally, you may find that you use a module’s functions so frequently that it pays to import it directly into the global namespace with
from
. - That’s fine but do so carefully and watch out for name conflicts.
Integrated Developer Environments (IDEs)¶
- Integrated Development Environments
- Programs to help you write programs
- Some recommended ones to check out...
- Microsoft Visual Studio Code - the dominant IDE nowadays
- IDX- cloud hosted VS Code from Google
- repl.it - cloud based multi-lingual IDE, great for learning
Homework¶
Question 1¶
Write a function called total
that returns the sum of a range of integers starting with the first passed value and ending at one less than the second passed value.
For example, total(4, 7)
returns 15
(4
+5
+6
).
def total(a, b):
# Add your code here
# Run this cell to test your code...
test_data = (
(4, 7),
(10, 12),
(1, 1),
(0, 0),
(0, 1),
(1, 0),
(-5, 2),
(-2, 2),
)
for i, (a, b) in enumerate(test_data):
computed = total(a, b)
actual = sum([i for i in range(a, b)])
assert computed == actual, f‘Test {i} failed: {computed} != {actual}’
print(‘All tests passed!’)
Question 2¶
Write a function called reverse
that returns a reversed copy of a passed string. For example, reverse('Obama')
returns amabO
.
# Add your code here
# Run this cell to test your code...
import random
import string
def reverse_test(a, b):
rev = reverse(a)
assert rev == b, f’{rev} != {b}’
# Some basic tests.
reverse_test(‘Obama’, ‘amabO’)
reverse_test(‘Marc’, ‘craM’)
reverse_test(’’, ’’)
reverse_test(‘aaa’, ‘aaa’)
reverse_test(‘A a’, ‘a A’)
# Test a very long string.
random_str = ’’
reverse_random_str = ’’
for i in range(1000):
ch = random.choice(string.ascii_lowercase)
random_str += ch
reverse_random_str = ch + reverse_random_str
reverse_test(random_str, reverse_random_str)
print(‘tests passed’)
Question 3¶
Import only the random()
function from the random
module. Write a function called rando
that returns, on average, True
half the time and False
half the time.
from random import random
def rando():
# Add your code here
return random() >= 0.5
# Run this cell to test your code...
import seaborn as sns
from random import randint
num_trials = 10000
width = 97
num_steps = 50
li = []
for i in range(num_trials):
position = width // 2
for j in range(num_steps):
if rando():
position += .5
else:
position -= .5
li.append(position)
#print(li)
sns.displot(li)
Question 4¶
Try some exercises on codingbat.com/python.
7 - 7 Tuples, Lists, and Dictionaries
Notebook 7 - Tuples, Lists, and Dictionaries¶
Tuples, Lists, and Dictionaries
Make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- mutability
- sequences
- tuples
- lists
- dictionaries
Mutability¶
Some data types can be changed (mutable) and some cannot. For example, strings are immutable. For this reason, you cannot assign values to the characters in a string. And when you call a function that seems to modify a string, it actually creates a new string and (normally) disposes of the old one.
s = 'Susan X. Anthony'
print('sixth char:', s[6])
s[6] = 'B'
print(s)
s = 'Susan B. Anthony'
print(s)
Tuples¶
Tuples are ordered collections, i.e. sequences, like strings, but they can contain any type of value (not just characters). They can even contain different types within the same tuple. Tuples are defined by parentheses (i.e. brackets) and the elements of a tuple are separated by commas, like this:
('a', 'b', 'c', 1, 2, 3)
.
Creating Tuples¶
# Create an empty tuple
tup = ()
print('empty tuple:', tup)
# Create a tuple with some initial contents
tup = ('Python', 1024, 3.14, True)
print('non-empty tuple:', tup)
# the same value can occur multiple times in a tuple
tup = ('a', 'a', 'a')
print(tup)
Tuple Operations¶
Because tuples are sequences, like strings, functions and for loops that operate on strings also play well with tuples, for example:
# the len function returns the length of a tuple
tup = (1, 2, 3, 4)
print(len(tup)) # returns size of a tuple
tup = (1, 2, 3, 4, 5)
print(len(tup))
# indexing (like strings, tuple offsets start with zero!)
tup = (1, 2, 3)
print(tup[0])
print()
print('the whole tuple...')
print(tup)
# indexing out of bounds raises a runtime error
tup = ('abc', 123, 3.14, True)
print(tup[99])
# loops
tup = (1, 2, 3)
for i in range(len(tup)):
print(tup[i])
# a better way to loop (a.k.a. iterate) over tuples...
tup = (1, 2, 3)
for i in tup:
print(i)
# the in operator (membership test)
x = 4
tup = (1, 2, 3, 4, 5)
if x in tup: # True if var’s value is in tuple
print(tup, 'contains ', x)
else:
print(tup, 'does NOT contain', x)
# slicing
tup = (1, 2, 3, 4, 5)
print(tup[2:]) # prints 3rd through end of tuple
print(tup[:3]) # prints first through third from last
print(tup[2:4])
# the plus operator concatenates (combines) two tuples into one
t1 = (1, 2, 3)
t2 = (4, 5, 6)
print(t1 + t2)
Tuples are Immutable¶
Like strings, once created, you can't change a tuple.
tup = (1, 2, 3)
print(tup[1])
tup[1] = 7
But you can assign a new tuple to the same variable. You haven't changed the tuple, you've changed the association between a variable and it's value.
tup = (1, 2, 3)
print(tup)
tup = (1, 3, 2)
# the tuple didn't change, the tup variable now points to different data!
print(tup)
Nested Tuples¶
Just as you can have if statements of if statements (nested if statements), and loops of loops (nested loops). You can have also have tuples of tuples (nested tuples).
- tuple of tuples:
((1, 2), (3, 4))
- tuple of strings and tuples:
('Hi', (1,2,3), "there")
- tuple of tuple of tuples: ```(((1,2), (3,4)), ((5,6), (7,8)))
# More readable tuple of tuples
nested_tuple = (
(
(1, 2),
(3, 4)
),
(
(5, 6),
(7, 8)
)
)
print(nested_tuple)
Lists¶
- A list is like a tuple but it's mutable (changeable).
- Almost everything you know about tuples also applies to lists.
- Lists are ordered sequences.
- All the sequence operations you learned about with strings and tuples, like
len
, indexing, slicing, loops, thein
operator, etc. apply to lists as well.
Lists are defined inside square brackets, with list elements separated by commas, for example...
['a', 'b', 'c', 1, 2, 3]
Creating Lists¶
# Create an empty list (lists use square brackets instead of parens)
li = []
print('empty list:', li)
# Create and initialize a list with some data
li = ['Python', 123, 3.14, True]
print('non-empty list:', li)
# the same value can occur multiple times in a list
li = ['a', 'a', 'a']
print(li)
List Operations¶
# The len() function gives us the size of a list.
li = ['abc', 123, 3.14, True, 5]
# get the size of a list
list_size = len(li)
print(list_size)
li = ['abc', 123, 3.14, True, 99, 101, "another", "last one - I promise"]
# iterate (loop) over the elements in a list
for i in range(len(li)):
print(li[i])
# A better way to iterate over the elements in a list
li = ['abc', 123, 3.14, True, 99, 101, "another", "last one - I promise"]
for i in li:
print(i)
li = ['abc', 123, 3.14, True]
# test membership in a list
x = 3.14159
if not x in li:
print(x, ‘is not in list’)
else:
print(x, ‘is in list’)
li = ['abc', 123, 3.14, True]
# indexing (list indexes start with zero!)
print(li[2])
# indexing out of bounds raises a runtime error
li = ['abc', 123, 3.14, True]
print(li[99])
li = ['abc', 123, 3.14, True]
# slicing
print(li[1:3])
# concatenating lists
li1 = ['a', 1]
li2 = ['b', 2]
li3 = [99.99]
li4 = li1 + li2 + li3
print(li4)
Lists are Mutable¶
Unlike tuples and strings, we can change the contents of a list after it's created.
# add an element
li = []
print(li)
li.append('elem1')
print(li)
li.append(400)
print(li)
li = ['elem1', 'elem2']
print(li)
# remove an element
li.remove('elem1')
print(li)
# removes only first occurrence of 'element' in list
# differs from del because it’s based on value, not position
li = ['elem1', 'elem2', 3, 99.9]
# remove & retrieve an element based on position
print(‘start:’, li)
for i in range(len(li)):
elem = li.pop()
print(‘removed’, elem, ‘remaining list:’, li)
# If you remove an element that doesn't exist, Python gives a run time error.
# How can that be avoided? Test for existence before removing by value
li = ['elem1', 'elem2']
e = 'elem3'
li.remove(e)
li = ['elem1', 'elem2', 'elem3']
print(li)
# replace an element by index
li[1] = 'foo' # overwrites value at index 2
print(li)
li = ['abc', 123, 3.14, True]
print(li[1:3])
# assign to a list slice
li[1:] = [9,8,7.6]
print(li)
li = ['abc', 123, 3.14, True]
# delete a list element
del li[2]
print(li)
li = ['abc', 123, 3.14, True]
# delete a list slice
del li[1:3]
print(li)
li = ["Maya", "Marc", "Kimba"]
print('original:', li)
# sorting a list
li.sort()
print(‘sorted:’, li)
# sorts in ascending order (small to large) by default
# to sort in descending order, use this syntax:
li.sort(reverse=True)
print(‘reverse sorted:’, li)
li.sort()
print(’re-sorted:’, li)
li = ['abc', 123, 3.14, 'abc', 'abc', True, 'abc', 123]
# get the number of occurrences of a particular value
count = li.count('abc')
print(count)
li = ['abc', 123, 3.14, True, 123]
# get the (first) index of a particular value
index = li.index(True)
print(index)
li = ['abc', 123, 3.14, True, 123]
# reverse a list
li.reverse()
print(li)
li.reverse()
print(li)
Nested Lists¶
Just as we saw tuples of tuples, we can also have also have lists of lists. In fact, we can even have lists of tuples and tuples of lists!
- list of lists:
[[1, 2], [3, 4]]
- list of tuples:
[(1, 2), (3,4)]
- tuple of lists:
([1, 2], [3, 4]])
We can even have lists of lists of tuples of lists of strings... you get the idea, this can get arbitrarily complex. Fortunately, most of the time you only need to use one or two levels, although occasionally you may need to go deeper.
Example¶
Imagine I want to maintain a list of students and their quiz scores. If I think about just one particular student, I might like to store the student's name and each quiz score up to the current lesson. I'd want to use a list because I'm going to want to add quiz results every week and, occasionally, I might need to change a grade.
Here's the data for one student...
student = [ 'Kimba', 95, 100, 90 ]
print(student)
But we have many students, so I need to store one instance of that list for every student. That collection also needs to be mutable, because I may need to add or delete students over time.
This seems like a job for a list of lists, like this one...
students = [
[ 'Kimba', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ],
[ 'Marc', 85, 90 ]
]
print(students)
We can access a sublist, i.e. one of the lists in this list of lists, like this...
students = [
[ 'Marc', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ]
]
student = students[0] # get data for Marc
print(student)
We can access an element of a sublist, like this...
# get Maya's second test score
students = [
[ 'Marc', 95, 100, 90 ],
[ 'Maya', 90, 95, 100 ]
]
score = students[1][2]
print(score)
Let's now generate a table of average scores for each student, effectively each student's final grade...
students = [
[ 'Kimba', 95, 100, 90, 98, 95, 99, 93, 97 ],
[ 'Marc', 90, 95, 100, 90, 100, 93, 74 ],
[ 'Maya', 90, 80, 100, 99, 815]
]
#print(students)
for student in students:
total = 0
count = len(student)
for score in student[1:]:
total += score
average = total / (count - 1)
print(student[0], average)
Dictionaries¶
A dictionary is an organized collection of key/value pairs. The data is organized for quick access via the key, somewhat like a real dictionary, where words are the keys and their definitions are the associated values.
Dictionaries are defined using curly braces with key:value pairs separated by colons, like this:
websites = {
'google': 'https://google.com',
'youtube': 'https://youtube.com',
'baidu': 'https://baidu.com',
}
This data type is known by various names in other languages:
- map (C++)
- hashmap (Java)
- associative array (generic term)
This object type is extremely powerful for representing indexed data. The keys in a dictionary are arranged to facilitate fast lookup by key value.
They are optimized for direct, not sequential, access
There is no implied order of keys or values
You can't index a dictionary by position
But you can index dictionaries by key value, as we’ll see
You can't take slices of a dictionary
Dictionaries are mutable, like lists, they can grow, shrink, or change over time
Dictionary keys must be immutable (e.g., string, number, tuple) because changing keys on the fly would confuse the dictionary.
Dictionary values can have any type (mutable or immutable).
Dictionary Operations¶
# Create an empty dictionary (use curly braces instead of parens or square brackets)
grades = {}
print(grades)
# Create and initialize a dictionary
grades = { 'Marc' : 95, 'Maya': 100 }
print(grades)
# If the same key occurs multiple times, python only keeps the last value
x = { 'a' : 1, 'a' : 2 }
print(x)
# but the same value may appear any number of times.
x = { 'a' : 1, 'b' : 1 }
print(x)
# Get the size of a dictionary (returns number of key/value pairs)
grades = { 'Marc' : 95, 'Maya': 100 }
print(len(grades))
# Retrieve the value associated with a given key
grades = { 'Marc' : 95, 'Maya': 100 }
grade = grades['Maya']
print(grade)
# The value inside the square brackets may be a literal, a variable or any
# arbitrary expression. Similar syntax to list/tuple indexing but key based,
# not positional.
grades = { 'Marc' : 95, 'Maya': 100 }
# Attempting to retrieve a non-existent key causes an error
x = 'Marc'
grades[x]
grades = { 'Marc' : 95, 'Maya': 100 }
student = 'foo'
# play it safe by testing for key existence before access
grade = grades[student]
if student in grades:
grade = grades[student]
print('grade for', student, 'is', grade)
else:
print('student', student, 'not found')
# When used with dictionaries, the in operator only checks the existence
# of keys, not values. You can also use “not in” to test for non-existence
# of a key.
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# loop through a dictionary (this iterates over the dictionary keys)
for i in grades:
print(i, grades[i])
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# By default, the keys will appear in random order. You can iterate keys in order by sorting them first:
for i in sorted(grades):
print(i, grades[i])
Dictionaries are Mutable¶
grades = { 'Marc' : 95, 'Maya': 100 }
print('before:', grades)
#Add a key/value pair
grades['Kimba'] = None # new, no grade yet
print('after:', grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': None }
print('before:', grades)
grades['Marc'] = 80 # grade recorded
print('after1:', grades)
grades['Marc'] += 5 # increment Marc's grade
print('after2:', grades)
grades['Kimba'] = 99 # change Kimba's grade
print('after2:', grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# delete a key/value pair
del grades['Marc'] # Marc dropped the course
print(grades)
grades = { 'Marc' : 95, 'Maya': 100, 'Kimba': 85 }
# Trying to delete a key that doesn't exist will cause a runtime error.
del grades['Fred']
Nested Dictionaries¶
Just like we can have nested if statements, nested loops, nested tuples, and nested lists, we can also have nested dictionaries.
- dictionary of tuples:
{'key1': (1,2), 'key2': (3,4)}
- dictionary of lists:
{'key1': [1,2], 'key2': [3,4]}
- dictionary of dictionaries:
{
'key1' : {
'key1' : [1, 2],
'key2' : [3, 4]
}
'key2' : {
'key1' : [1, 2],
'key2' : [3, 4]
}
}
This can get arbitrarily complex (dictionaries of lists of tuples of dictionaries of...).
Once again, imagine I want to maintain a list of students and their quiz scores. If I think about just one particular student, I might like to store the student's name and each quiz score up to the current lesson. I need a mutable sequence (i.e. a list) because I'm going to want to add quiz results every week, like this:
student = [ 'Jeff', 95, 100, 90 ]
I'd like to organize the test scores by student name that way I can efficiently find any given student's scores by their name (i.e. by indexing on the dictionary key).
This leads us to a dictionary of lists:
grades = {
'Marc' : [95, 100, 90],
'Maya' : [90, 95, 100]
}
To add a student:
grades[student] = []
To delete a student:
del grades[student]
To add a new score for a student:
grades[student].append(new_score)
To replace the 2nd quiz score for a student:
grades[student][1] = new_score
Rule of thumb for truth value of tuples, lists, and dictionaries¶
All of these objects may be used as boolean values. The rules for converting a tuple, list, or map into a boolean value are as follows:
- if the object is empty, it evaluates to False
- if the object is non-empty, it evaluates to True
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘tuple test…’)
for i in (), (1,2,3), (‘a’, 1), (None,):
print(i, empty(i))
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘list test…’)
for i in [], [1,2,3], [‘a’, 1], [None]:
print(i, empty(i))
def empty(collection):
if collection:
return 'is NOT empty.'
else:
return 'is empty.'
print(‘dictionary test…’)
for i in {}, {1: 2, 3: 4}, {‘a’:1, ‘b’:2, ‘c’:3}, {None:None}:
print(i, empty(i))
Homework¶
Question 1¶
I have a list of things I need to buy from my supermarket of choice.
I want to know what the first thing I need to buy is. However, when I run the program it shows me a different answer than what I was expecting. What is the mistake? Can you fix it in the cell below?
shopping_list = [
"oranges",
"cat food",
"sponge cake",
"long-grain rice",
"cheese board",
]
print(shopping_list[1])
#@title Double click here to reveal solution
print(shopping_list[0]) # because sequence indexes start with zero
Question 2¶
I'm setting up my own market stall to sell chocolates. I need a basic till to check the prices of different chocolates that I sell. I've started the program and included the chocolates and their prices. Finish the program by asking the user to input an item and then output its price.
chocolates = {
'white': 1.50,
'milk': 1.20,
'dark': 1.80,
'vegan': 2.00,
}
# Add your code here.
#@title Double click here to reveal solution
chocolates = {
‘white’: 1.50,
‘milk’: 1.20,
‘dark’: 1.80,
‘vegan’: 2.00,
}
while True:
item = input(“Enter the desired item (q to quit): “).lower()
if item == ‘q’:
break
if item in chocolates:
print(‘The price for {} chocolate is £{:.2f}.\n’.format(item, chocolates[item]))
else:
print(‘Sorry, we don't sell {} chocolate.\n’.format(item))
Question 3¶
Write a function named enumerate()
that takes a list of strings and enumerates them, i.e. it returns a list where each passed string is prefixe by a sequential number, starting at 1. For example...
li = ['test', 'another test', 'last test']
results = enumerate(li)
for i in results:
print(i)
should produce this output:
1. test
2. another test
3. last test
Question 4¶
Write a program that simulates a lottery. The program should have a list of seven numbers that represent a lottery ticket. It should then generate seven random numbers. After comparing the two sets of numbers, the program should output a prize based on the number of matches:
- £20 for three matching numbers
- £40 for four matching numbers
- £100 for five matching numbers
- £10000 for six matching numbers
- £1000000 for seven matching numbers
# Add your code here
#@title Double click here to reveal solution.
import random
my_numbers = random.choices(range(1, 99), k=7)
print(‘your numbers are: ‘, my_numbers)
winning_numbers = random.choices(range(1, 99), k=7)
print(‘winning numbers are: ‘, winning_numbers)
hits = 0
for i in my_numbers:
if i in winning_numbers:
hits += 1 # shorthand for hits = hits + 1
prizes = {
3: 20,
4: 40,
5: 100,
6: 10000,
7: 1000000
}
if hits in prizes:
print(’** You matched {} numbers and won £{}! **’.format(hits, prizes[hits]))
else:
print(‘Sorry, no prize won, better luck next time!’)
8 - 8 Files, Errors, and Exceptions
Notebook 8 - Files, Errors, and Exceptions¶
Make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- reading and writing files
- exceptions and error handling strategies
Reading and Writing Files¶
Our programs have amnesia¶
- Program variables reside in memory, and main memory is not persistent, so when you close a notebook, or terminate a program running locally, your data disappears.
- Imagine having to re-enter your contact list every time you use your phone.
- We'll need a way to store and retrieve data.
Storage Tradeoffs¶
- There are two kinds of storage in your computer:
- main memory is fast, but transient (like human memory)
- disk storage is slow(er), but permanent (like a notebook) and higher capacity
- All the things we've worked with so far (variables, functions, program statements) reside in main memory.
- We'll save information across program executions using disk storage in units we call files.
What is a file anyway?¶
- A named chunk of stored data is called a
file
. - Files are organized into hierarchical structures, called directories or folders.
- Examples:
- Windows:
c:\Users\marccohen\my_fave_movies.md
- Mac/Linux:
/Users/marccohen/my_fave_movies.md
- Windows:
path
is the file's location, e.g.c:\Users\marccohen\
- it's the "where"
filename
is the file's name, e.g.my_fave_movies.md
- it's the "which"
King Charles -----> the which
Buckingham Palace \
London, UK |---> the where
SW1A 1AA. /
Opening a File¶
- Before you can read or write a file, you need to open it.
- Use the
open()
function to open a file. - prototype:
variable = open(filename, mode)
- example:
file = open("myfile", "r")
- The first argument is a file specificaton, which can include a path or not.
- If no path is provided, the filename is assumed to reside in the current directory/folder.
- We'll cover the second argument, the mode, in the next cell.
- Open returns a special type, called a file object, which is used for subsequent operations on the file.
File Access Modes¶
Mode | Description | access | if file exists... | if file doesn't exist... |
---|---|---|---|---|
"r" | read from a file | read | open file | generate error |
"w" | write to a file | write | overwrite & open | create file & open |
"a" | append to a file | write | open for append | create file & open |
"r+" | read/write from/to a file | read/write | open file | generate error |
"w+" | write/read from/to a file | read/write | overwrite & open | create file & open |
"a+" | append/read a text file | read/write | open for append | create file & open |
Closing a File¶
- the opposite of
open()
isclose()
- when you're done working with a file, you should close it
- closing a file cleans up the loose ends
close()
is a method of the file object- example:
file.close()
Writing to a File¶
file.write('this is a line of text\n')
file
must have been opened with write or append access- writes the passed string into the file
- you have to include newline characters where you want them, otherwise subsequent write calls will build one long line
- writes may not be visible until you close the file
f = open('test.txt', 'w')
f.write('This is my test file.\n')
for i in range(10):
f.write('line number ' + str(i) + '\n')
f.close()
Reading From a File¶
mystr = file.read()
- file must have been opened with read access
- reads the entire file into memory
- the result is returned in a string
- you can pass an argument to limit how many characters are read
f = open('test.txt', 'r')
s = f.read()
f.close()
print(s)
Reading a file iteratively¶
for line in file:
- this iterates over the lines in a file
- each iteration of the loop reads a line from the file and sets the loop variable (
line
in this case) to the string value of each line in the file - the string includes the trailing newline
- this is a very handy way of processing a text file one line at a time
- also space-efficient because it only needs to store one line at a time in main memory
! cat test.txt # Display current contents of test.txt file.
myfile = open('test.txt', 'r')
for text in myfile:
print(text, end='')
myfile.close()
The with
Statement¶
- automatically ensures files get closed (and any other resoures get cleaned up)
- without the
with
statement...
file = open('file_path', 'w')
file.write('Hello world!')
file.close()
- using the
with
statement...
with open('file_path', 'w') as file:
file.write('Hello world!')
with open('test.txt', 'r') as f:
for line in f:
print(line, end='')
Summary of File Functions and Methods¶
open()
- open a fileclose()
- close a fileread(n)
- read up to n chars from current position to end of file and return in a string. if n not provided, read all chars from current position to end of file.write(s)
- write string s to a file- There are many more functions and methods available to operate on files in Python. You can learn more here.
Errors¶
This section is derived from work that is Copyright (c) The Carpentries.
Errors in Python have a very specific form, called a traceback. Let's examine one:
# This code has an intentional error. You can type it directly or
# use it for reference to understand the error message below.
def favorite_ice_cream():
ice_creams = [
'chocolate',
'vanilla',
'strawberry'
]
print(ice_creams[3])
favorite_ice_cream()
When run, this code produces the following result:
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-1-70bd89baa4df> in <module>()
9 print(ice_creams[3])
10
----> 11 favorite_ice_cream()
<ipython-input-1-70bd89baa4df> in favorite_ice_cream()
7 ‘strawberry’
8 ]
—-> 9 print(ice_creams[3])
10
11 favorite_ice_cream()
IndexError: list index out of range
This particular traceback has two levels.
The first shows code from the cell above, with an arrow pointing to Line 11 (which is
favorite_ice_cream()
).The second shows some code in the function
favorite_ice_cream
, with an arrow pointing to Line 9 (which isprint(ice_creams[3])
).
The last level is where the error occurred. The other level(s) show what function the program executed to get to the next level down.
So, in this case, the program first called the function favorite_ice_cream
. Inside this function, the program encountered an error on Line 6, when it tried to run the code print(ice_creams[3])
.
So what error did the program actually encounter?
In the last line of the traceback,
Python helpfully tells us the category or type of error (in this case, it is an IndexError
)
and a more detailed error message (in this case, it says "list index out of range").
If you encounter an error and don't know what it means, it is still important to read the traceback closely. That way, if you fix the error, but encounter a new one, you can tell that the error changed. Additionally, sometimes knowing where the error occurred is enough to fix it, even if you don't entirely understand the message.
If you do encounter an error you don't recognize, try looking at the official documentation on errors. However, note that you may not always be able to find the error there, as it is possible to create custom errors. In that case, hopefully the custom error message is informative enough to help you figure out what went wrong.
Syntax Errors¶
When you forget a colon at the end of a line,
accidentally add one space too many when indenting under an if
statement, or forget a parenthesis,
you will encounter a syntax error.
This means that Python couldn't figure out how to read your program.
This is similar to forgetting punctuation in English:
for example,
this text is difficult to read there is no punctuation there is also no capitalization
why is this hard because you have to figure out where each sentence ends
you also have to figure out where each sentence begins
to some extent it might be ambiguous if there should be a sentence break or not
People can typically figure out what is meant by text with no punctuation, but people are much smarter than computers. If Python doesn't know how to read the program, it will give up and inform you with an error. For example:
def some_function()
msg = 'hello, world!'
print(msg)
return msg
File "<ipython-input-3-6bb841ea1423>", line 1
def some_function()
^
SyntaxError: invalid syntax
Here, Python tells us that there is a SyntaxError
on line 1,
and even puts a little arrow in the place where there is an issue.
In this case the problem is that the function definition is missing a colon at the end.
Actually, the function above has two issues with syntax.
If we fix the problem with the colon,
we see that there is also an IndentationError
,
which means that the lines in the function definition do not all have the same indentation:
def some_function():
msg = 'hello, world!'
print(msg)
return msg
File "<ipython-input-4-ae290e7659cb>", line 4
return msg
^
IndentationError: unexpected indent
Both SyntaxError
and IndentationError
indicate a problem with the syntax of your program,
but an IndentationError
is more specific:
it always means that there is a problem with how your code is indented.
Tabs and Spaces¶
Some indentation errors are harder to spot than others.
In particular, mixing spaces and tabs can be difficult to spot
because they are both whitespace.
In the example below, the first two lines in the body of the function
some_function
are indented with tabs, while the third line — with spaces.
def some_function():
msg = 'hello, world!'
print(msg)
return msg
Visually it is impossible to spot the error. Fortunately, Python does not allow you to mix tabs and spaces.
File "<ipython-input-5-653b36fbcd41>", line 4
return msg
^
TabError: inconsistent use of tabs and spaces in indentation
Variable Name Errors¶
Another very common type of error is called a NameError
,
and occurs when you try to use a variable that does not exist.
For example:
print(a)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-7-9d7b17ad5387> in <module>()
----> 1 print(a)
NameError: name ‘a’ is not defined
Variable name errors come with some of the most informative error messages, which are usually of the form "name 'the_variable_name' is not defined".
Why does this error message occur? That's a harder question to answer, because it depends on what your code is supposed to do. However, there are a few very common reasons why you might have an undefined variable. The first is that you meant to use a string, but forgot to put quotes around it:
print(hello)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-8-9553ee03b645> in <module>()
----> 1 print(hello)
NameError: name ‘hello’ is not defined
The second reason is that you might be trying to use a variable that does not yet exist.
In the following example,
count
should have been defined (e.g., with count = 0
) before the for
loop:
for number in range(10):
count = count + number
print('The count is:', count)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-9-dd6a12d7ca5c> in <module>()
1 for number in range(10):
----> 2 count = count + number
3 print('The count is:', count)
NameError: name ‘count’ is not defined
Finally, the third possibility is that you made a typo when you were writing your code.
Let's say we fixed the error above by adding the line Count = 0
before the for loop.
Frustratingly, this actually does not fix the error.
Remember that variable names are case-sensitive,
so the variable named count
is different from Count
. We still get the same error,
because we still have not defined count
:
Count = 0
for number in range(10):
count = count + number
print('The count is:', count)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-10-d77d40059aea> in <module>()
1 Count = 0
2 for number in range(10):
----> 3 count = count + number
4 print('The count is:', count)
NameError: name ‘count’ is not defined
Index Errors¶
Next up are errors having to do with containers (like lists and strings) and the items within them. If you try to access an item in a list or a string that does not exist, then you will get an error. This makes sense: if you asked someone what day they would like to get coffee, and they answered "caturday", you might be a bit annoyed. Python gets similarly annoyed if you try to ask it for an item that doesn't exist:
letters = ['a', 'b', 'c']
print('Letter #1 is', letters[0])
print('Letter #2 is', letters[1])
print('Letter #3 is', letters[2])
print('Letter #4 is', letters[3])
Letter #1 is a
Letter #2 is b
Letter #3 is c
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-11-d817f55b7d6c> in <module>()
3 print('Letter #2 is', letters[1])
4 print('Letter #3 is', letters[2])
----> 5 print('Letter #4 is', letters[3])
IndexError: list index out of range
Here,
Python is telling us that there is an IndexError
in our code,
meaning we tried to access a list index that did not exist.
File Errors¶
The last type of error we'll cover today
are those associated with reading and writing files: FileNotFoundError
.
If you try to read a file that does not exist,
you will receive a FileNotFoundError
telling you so.
If you attempt to write to a file that was opened read-only, Python 3
returns an UnsupportedOperationError
.
More generally, problems with input and output manifest as
IOError
s or OSError
s, depending on the version of Python you use.
file_handle = open('myfile.txt', 'r')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-14-f6e1ac4aee96> in <module>()
----> 1 file_handle = open('myfile.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: ‘myfile.txt’
One reason for receiving this error is that you specified an incorrect path to the file.
For example,
if I am currently in a folder called myproject
,
and I have a file in myproject/writing/myfile.txt
,
but I try to open myfile.txt
,
this will fail.
The correct path would be writing/myfile.txt
.
It is also possible that the file name or its path contains a typo.
A related issue can occur if you use the "read" flag instead of the "write" flag.
Python will not give you an error if you try to open a file for writing
when the file does not exist.
However,
if you meant to open a file for reading,
but accidentally opened it for writing,
and then try to read from it,
you will get an UnsupportedOperation
error
telling you that the file was not opened for reading:
file_handle = open('myfile.txt', 'w')
file_handle.read()
---------------------------------------------------------------------------
UnsupportedOperation Traceback (most recent call last)
<ipython-input-15-b846479bc61f> in <module>()
1 file_handle = open('myfile.txt', 'w')
----> 2 file_handle.read()
UnsupportedOperation: not readable
These are the most common errors with files, though many others exist. If you get an error that you've never seen before, searching the Internet for that error type often reveals common reasons why you might get that error.
Challenges¶
Reading Error Messages¶
Read the Python code and the resulting traceback below, and answer the following questions:
- How many levels does the traceback have?
- What is the function name where the error occurred?
- On which line number in this function did the error occur?
- What is the type of error?
- What is the error message?
# This code has an intentional error. Do not type it directly;
# use it for reference to understand the error message below.
def print_message(day):
messages = {
'monday': 'Hello, world!',
'tuesday': 'Today is Tuesday!',
'wednesday': 'It is the middle of the week.',
'thursday': 'Today is Donnerstag in German!',
'friday': 'Last day of the week!',
'saturday': 'Hooray for the weekend!',
'sunday': 'Aw, the weekend is almost over.'
}
print(messages[day])
def print_friday_message():
print_message(‘Friday’)
print_friday_message()
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-1-4be1945adbe2> in <module>()
14 print_message('Friday')
15
---> 16 print_friday_message()
<ipython-input-1-4be1945adbe2> in print_friday_message()
12
13 def print_friday_message():
---> 14 print_message('Friday')
15
16 print_friday_message()
<ipython-input-1-4be1945adbe2> in print_message(day)
9 'sunday': 'Aw, the weekend is almost over.'
10 }
---> 11 print(messages[day])
12
13 def print_friday_message():
KeyError: 'Friday'
Solution¶
- 3 levels
print_message
- 11
KeyError
- There isn't really a message; you're supposed to infer that
Friday
is not a key inmessages
.
Identifying Syntax Errors¶
- Read the code below, and (without running it) try to identify what the errors are.
- Run the code, and read the error message. Is it a
SyntaxError
or anIndentationError
? - Fix the error.
- Repeat steps 2 and 3, until you have fixed all the errors.
def another_function
print('Syntax errors are annoying.')
print('But at least Python tells us about them!')
print('So they are usually not too hard to fix.')
Solution¶
SyntaxError
for missing ():
at end of first line,
IndentationError
for mismatch between second and third lines.
A fixed version is:
def another_function():
print('Syntax errors are annoying.')
print('But at least Python tells us about them!')
print('So they are usually not too hard to fix.')
Identifying Variable Name Errors¶
- Read the code below, and (without running it) try to identify what the errors are.
- Run the code, and read the error message.
What type of
NameError
do you think this is? In other words, is it a string with no quotes, a misspelled variable, or a variable that should have been defined but was not? - Fix the error.
- Repeat steps 2 and 3, until you have fixed all the errors.
for number in range(10):
# use a if the number is a multiple of 3, otherwise use b
if (Number % 3) == 0:
message = message + a
else:
message = message + 'b'
print(message)
Solution¶
3 NameError
s for number
being misspelled, for message
not defined,
and for a
not being in quotes.
Fixed version:
message = ''
for number in range(10):
# use a if the number is a multiple of 3, otherwise use b
if (number % 3) == 0:
message = message + 'a'
else:
message = message + 'b'
print(message)
Identifying Index Errors¶
- Read the code below, and (without running it) try to identify what the errors are.
- Run the code, and read the error message. What type of error is it?
- Fix the error.
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
print('My favorite season is ', seasons[4])
Solution¶
IndexError
; the last entry is seasons[3]
, so seasons[4]
doesn't make sense.
A fixed version is:
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
print('My favorite season is ', seasons[-1])
Exceptions¶
When a Python program has an error, something called an exception is raised. If nothing special is done to handle the exception, the program stops running and an error message describing the exception is displayed, like this:
>>> int('x')
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
int('x')
ValueError: invalid literal for int() with base 10: 'x'
With Python's exception handling facilities, you can:
- handle exceptions before they stop your program
- raise your own exceptions
- structure your error handling code in a simpler, more natural way
Exception Handling¶
Here's how to handle (or catch) an exception in Python:
try:
<block of code>
except:
<exception handler block of code>
How do we delineate the scope of the try
and except
blocks? As usual, by indentation.
Python tries to run the try
block.
If that causes an exception, then it runs the except
block.
if no exception is generated by the try
block, the except
block is skipped.
Exception Handling Example¶
Here's an example exception handler:
resp = input('enter an integer: ')
try:
num = int(resp)
except:
print('problem converting', resp, 'to int')
This code handles every possible exception type. You can specify a specific exception type you want to handle by including it after the except keyword, like this:
except ValueError: # handle ValueError exceptions only
Common Exception Types¶
Exception Type | Description |
---|
Exception|base type of all exceptions IOError|I/O operation failed IndexError|invalid index applied to a sequence KeyError|invalid key applied to a dictionary NameError|invalid or unknown variable or function name SyntaxError|invalid Python language syntax encountered TypeError|operator or function applied to inappropriate type ValueError|operator or function applied to invalid value ZeroDivisionError|division or modulus by zero
Handling Multiple Exceptions¶
You can handle multiple exception types with one except clause by specifying a comma separated list of exception types in parentheses (aka, brackets), like this:
except (TypeError, ValueError):
You can also use multiple except clauses in a single statement, like this:
def convert(param):
try:
value = int(param)
return value
except TypeError:
print('can\'t convert', type(param), 'to int')
except ValueError:
print('can\'t convert', param, 'to int')
Getting Exception Arguments¶
Exceptions often come with data passed as an exception argument, which can be obtained like this:
except ValueError as msg:
print(msg)
For example, this code:
try:
int('x')
except ValueError as msg:
print(msg)
displays this message:
invalid literal for int() with base 10: 'x'
Exceptions Can Have Else Clauses¶
An optional else clause, if included, is executed if no exception occurs, for example:
try:
int(resp)
except ValueError as msg:
print(msg)
else:
print('conversion succeeded')
This statement is certain to display one (and only one!) of the two prints statements above. What it won't do, is terminate your program due to a conversion error.
How to Raise an Exception¶
You can raise your own exceptions, anywhere in a Python program, using the raise
statement, like this:
raise <exception type>
or like this, to pass an argument along with the exception:
raise <exception type>(<argument>)
this causes an exception to be raised. Control resumes at the first enclosing code (in inner-to-outer order) that handles the raised exception type. If no enclosing code handles the exception, Python terminates the program and displays information about the exception.
Structuring Code Around Exceptions¶
What happens if we pass an empty list to this function:
def avg(numbers):
sum = 0
cnt = 0
for i in numbers:
sum += i
cnt += 1
return sum // cnt
We can use exceptions to catch errors like this:
try:
result = avg([])
except:
print('something went wrong')
Instead of writing code like this:
if choice == "a":
word = input('word to add: ')
err = add_word(word)
if (err):
print(err)
else:
print(word, 'successfully added')
elif choice == "d":
word = input('word to delete: ')
err = del_word(word)
if (err):
print(err)
else:
print(word, 'successfully deleted')
We can write code like this (one exception handler vs. multiple ifs):
try:
if choice == 'a':
word = input('word to add: ')
add_word(word)
print(word, 'successfully added')
elif choice == 'd':
word = input('word to delete: ')
del_word(word)
print(word, 'successfully deleted')
except Exception as msg:
print('ERROR:', msg)
Error Handling Styles¶
Style 1 (nested):
def add_friend(user, friend):
if user in users:
if friend in users:
if friend not in users[user]:
users[user].append(friend)
else:
raise Err('friend already on list')
else:
raise Err('unregistered friend name')
else:
raise Err('unregistered user name')
Style 2 (linear):
def add_friend(user, friend):
if user not in users:
raise Err('unregistered user name')
elif friend not in users
raise Err('unregistered friend name')
elif friend in users[user]
raise Err('friend already on list')
users[user].append(friend)
return None
I like this style because I find it more readable. Why is readability so important? Readable code is maintainable code.
Defining and Using Our Own Exceptions¶
We can limit the scope of our exception handler to our own exceptions, like this:
# define a new type of exception called avgError
class avgError(Exception):
pass
def avg(numbers):
if len(numbers) <= 0:
raise avgError(’empty sequence not supported’)
for i in numbers:
sum += i
cnt += 1
return sum // cnt
In calling code:
try:
result = avg([])
except avgError as msg:
print('ERROR:', msg)
This is just like our previous version except that we're handling an application specific exception. Other exceptions will not be caught, which is good (why?).
Homework¶
Question 1¶
Write a function called write_file() that takes two arguments: a filename and a list of strings, opens the named file for write access and uses a for
loop to write the list contents into the file, one string per line.
For example:
li = ['test', 'another test', 'last test']
write_file('output.txt', li)
Using your systems file explorer or command line, verify the file was created and has the expected contents. If you're not sure how to do that, you could also use your new read_file()
function!
9 - 9 Sample Project
Notebook 9 - Sample Project¶
Make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- example code and philosophy from a real Python project
Introducing Quizaic¶
Application Architecture¶
Web Server¶
https://github.com/mco-gh/quizaic/blob/main/api/main.py
from flask import Response, Flask, g, request
resource = [
“admins”,
“quizzes”,
“results”,
“sessions”,
“generators”,
]
app = Flask(name)
@app.route(”/<resource_name>", methods=[“GET”])
def handle_list(resource_name):
if resource_name not in resource:
return “Not found”, 404
return methods.list(resource_name)
Get a Resource¶
https://github.com/mco-gh/quizaic/blob/main/api/resources/methods.py
def get(resource_kind, id):
log(f"Request to get {resource_kind}", severity="INFO")
if resource_kind not in resource_fields:
return "Not found", 404, {}
if resource_kind == "sessions" and id == "me":
id = get_hashed_email()
result = db.fetch(resource_kind, id, resource_fields[resource_kind])
if result is None:
return "Not found", 404, {}
return (
json.dumps(result),
200,
{"Content-Type": "application/json", "ETag": base.etag(result)},
)
Access Checking¶
https://github.com/mco-gh/quizaic/blob/main/api/resources/auth.py
def user_logged_in(email):
return email != None
def user_created_quiz(hashed_email, quiz_id):
if hashed_email is None:
return False
quiz = db.fetch(“quizzes”, quiz_id, [“creator”])
if quiz and quiz[“creator”] == hashed_email:
return True
return False
Generate a Quiz¶
https://github.com/mco-gh/quizaic/blob/main/api/pyquizaic/generators/quiz/gemini-pro/quizgen.py
def gen_quiz(
self,
topic=BaseQuizgen.TOPIC,
num_questions=BaseQuizgen.NUM_QUESTIONS,
num_answers=BaseQuizgen.NUM_ANSWERS,
difficulty=BaseQuizgen.DIFFICULTY,
language=BaseQuizgen.LANGUAGE,
temperature=BaseQuizgen.TEMPERATURE,
):
# print(f"{topic=}, {num_questions=}, {num_answers=}, {difficulty=}, {language=}")
file_path = os.path.join(os.path.dirname(__file__), f"../prompt.txt")
with open(file_path, encoding="utf-8") as fp:
self.prompt_template = fp.read()
prompt = self.prompt_template.format(
topic=topic,
num_questions=num_questions,
num_answers=num_answers,
language=language,
difficulty=difficulty,
)
prediction = self.predict_llm(
MODEL, prompt, temperature, MAX_OUTPUT_TOKENS, TOP_P, TOP_K
)
prediction = prediction.strip()
prediction = re.sub('.*``` *(json)?', '', prediction)
prediction = prediction[prediction.find('['):]
parsed = ""
level = 0
for i in prediction:
if i == "[":
level += 1
elif i == "]":
level -= 1
parsed += i
if level <= 0:
break
prediction = parsed
print("prediction=", prediction)
quiz = json.loads(prediction)
#print(f"{quiz=}")
# Make sure the correct answer appears randomly in responses
for i in quiz:
random.shuffle(i["responses"])
return quiz