Objectives¶
In this lesson, we will introduce fundamental control structures and basic data types for Python. By the end of this lesson, students will be able to:
Evaluate expressions involving arithmetic and boolean operators.
Apply
forloops to iterate over a range of numbers increasing/decreasing by a fixed step size.Apply
whileloops to iterate until reaching a desired program condition.Evaluate expressions involving strings by applying
stroperations and slicing.
In this class, we will use a combination of Jupyter notebooks and Python scripts for lessons and coding practice.
In a Jupyter notebook, you will find a combination of text-based Markdown cells and runnable Python snippets, which we call code cells. To edit a Markdown cell, double-click the text to edit it. Then, use the keyboard shortcut Ctrl + Enter to apply your changes. This is a great way to take notes on the course content, if you wish.
You can find a list of Markdown formatting tips here!
Python Basics¶
Printing¶
Printing looks a bit different in Python than in other languages. To print any text, we will use the print function.
print("Hello, world!")Hello, world!
Try changing “world” to your name, or to the name of a friend!
One thing that Python is really good at is providing slightly different ways of using the same syntax to do helpful things. For example, you can also pass multiple values to the print function and it adds spaces between them!
print('Hello', 'world', '!')Hello world !
Quotation Marks
It generally doesn’t matter whether you use single quotes ('') or double quotes ("") for print statements. However, if the string you’re trying to print uses one type of quotation mark, use the other to indicate the string itself! Example: 'Say "what?"' or "What's that?"
Main-method pattern¶
A Python program is a series of statements that are executed from top to bottom. We will learn lots of different statements in this course!
In CSE 163, we will ask you to put a little bit of starter code in every Python script you write. We call this the main-method pattern. We can’t really motivate why you need to use this quite yet; you’ll have to trust us that it is the right thing to do. We introduce this pattern now to get you in the practice of writing from the onset of your Python journey. We will come back in future weeks and actually dive into what this pattern does and why it’s necessary.
Recall that we could write a Python program in a code cell to print “Hello world” like the following:
print("Hello, world!")Hello, world!
Note that if you tried to run this in a Python script or a code cell of a Jupyter notebook, this will run either way. However, instead, we will commonly ask you to write a few extra lines of code “around” the program you wanted to write as the following.
def main():
# Put your code, indented inside here
print('Hello world!')
if __name__ == '__main__':
main()Hello world!
(Note the comment in the second line, which is prefaced by a #!)
We’ll start you off by providing the main-method pattern as starter code for lessons and practice, but you will get used to writing these weird symbols by yourself! Again, we promise to explain this later, we just don’t have everything we need for that explanation right now!
For individual code snippets in Jupyter notebooks, the main-method pattern will be omitted. It is not needed for the Jupyter notebook, and we omit it from lessons to keep things more readable. For any Python script that you write in this class, including for homework assignments, you will always need to use the main-method pattern for these files.
Variables¶
Variables store values. Each Python value is composed of a value and its type. Unlike Java and older languages like C, Python doesn’t require you to define the type of a variable. A variable’s type is determined by the value it references. Additionally, throughout the lifetime of the program running, the variable can be made to hold a new value of a different type using an assignment statement.
The following snippet creates a variable named x that stores the value 3 of type int (for integer) and a variable named y that stores the value 4.2 of type float (for floating-point number). One line 3, it then re-assigned x to store the value 3.7 of type float. This is why the program prints x = 3.7 and y = 4.2.
x = 3
y = 4.2
x = 3.7
print('x =', x)
print('y =', y)x = 3.7
y = 4.2
In a Jupyter notebook, you can display the output by writing the variable name in a code cell and evaluating that code cell.
x3.7Expressions¶
Python supports many operations for built-in types. Specifically, here are the operations defined for numeric types like int and float.
Addition:
a + bSubtraction:
a - bMultiplication:
a * bDivision:
a / b(e.g., 7 / 3 == 2.333333333)Integer division:
a // b(e.g., 7 // 3 == 2)Mod:
a % b(i.e., leftover from integer division as in 7 % 3 == 1)Exponentiation:
a ** b(i.e., )
You can also nest expressions (and use parentheses to define order) since all expressions evaluate to some value.
a = 3
print(a - (2 * a) + (a ** (1 + 2)))24
What do you think is the output of this print statement?
Types and booleans¶
In addition to string and numeric types, Python also has a bool type (equivalent to Java’s boolean) that only takes on the values True or False.
Just like any other value, a bool can be stored in variables:
b1 = True
b2 = False
print(b1, b2)True False
They have operations:
a and b:Trueif and only if both values areTruea or b:Trueif either or both values areTruenot a: “flips” to the opposite boolean value
And they can be created by doing comparisons between other values. Just like how you can create a new numeric value from evaluating arithmetic operations, you can create a bool value by using logical operations.
x = 3
print(x < 4) # "Is x is less than 4?"
print(x >= 5) # "Is x greater than or equal to 5?"
print(x == 2) # "Is x equal to 2?"
print(x != 2) # "Is x not equal to 2?"True
False
False
True
While loops¶
If you want to repeat some computation, a programming language usually provides a construct called a loop that lets you repeat code some number of times.
A while loop has a condition and a body. The while loop proceeds in iterations, each iteration executes the body only if the condition is True, otherwise the loop ends. In general, a while loop looks like:
while condition:
# Loop body
statement
statement
statementNote the indentation and the colon at the end of the condition! Pythonic syntax is sensitive to whitespace, so in order to make sure the body is properly defined, it needs to be indented under the condition.
As an example, let’s take a look at this while loop:
x = 1
while x < 100:
print(x)
x = x * 2
print('After loop', x)1
2
4
8
16
32
64
After loop 128
The condition here is x < 100, so the loop keeps executing the body (print(x) and x = x * 2) until the next iteration it is False (when x = 128). After the loop ends, it continues on to the code after the loop: print('After loop', x).
Food for thought: What happens if you indent that final print statement?
For loops¶
Another type of loop that you’ll commonly see in Python is the for loop. The for loop has a body that runs for each item in a sequence and uses a loop variable to keep track of the current item.
We’ll start by showing an example and then explain the parts.
for i in range(5):
print('Loop', i)Loop 0
Loop 1
Loop 2
Loop 3
Loop 4
The for loop has the following components
range(5)describes the sequence of values we want to use. In this case,range(5)means the values0, 1, 2, 3, 4.We will explainrangein the next section.iis the loop variable that can be used in the body. On the first iteration,i = 0; theni = 1on the next; and so on until the last iteration, wherei = 4.print('Loop', i)is the body.
The for loop operates very similarly to the while loop, but the key difference is it will loop over the sequence of values specified after the in keyword. Just like the while loop, you put a : at the end of the line containing the keyword for and the body is indented inside the loop.
range Function¶
range is a function in Python provided to make it easy to make sequences of numbers in a range. It turns out, there are three different ways to call range that let you do slightly different types of loops!
# From 0 (inclusive) to A (exclusive)
for i in range(10):
print('Loop', i)Loop 0
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
Loop 6
Loop 7
Loop 8
Loop 9
# From A (inclusive) to B (exclusive)
for i in range(2, 10):
print('Loop', i)Loop 2
Loop 3
Loop 4
Loop 5
Loop 6
Loop 7
Loop 8
Loop 9
# From A (inclusive) to B (exclusive) using step size C
for i in range(2, 10, 3):
print('Loop', i)Loop 2
Loop 5
Loop 8
Food for thought: What do you think would happen if any of these values were negative?
Conditionals¶
Conditional statements let you execute code conditionally based on some condition; they are similar in nature to the while loop but only run at most once.
In Python, the keywords to control these conditionals are if, elif (read as “else if”), and else.
A conditional block is an if block optionally followed by any number of elif blocks optionally followed by at most one else block. Let’s look at an example:
x = 14
if x < 10:
print('A')
elif x >= 13:
print('B')
elif x >= 20:
print('Not possible')
else:
print('C')B
Let’s follow along to see what is printed:
We define
x = 14.Check if
x < 10. Since 14 is not less than 10, we skip the body of this conditional statement and move on to the next.Check if
x >= 13. Since 14 is greater than 13, we move into the body of this conditional statement, which says that we shouldprint('B')We are done!
Food for thought: Why is it impossible to enter the elif x >= 20 statement? What could we do instead to print the string 'Not possible'?
Functions¶
A function is a named procedure with a series of instructions that can be called in your program to execute those instructions.
To call a function, use its name and use () after it to make it a call For example, print is actually a function defined by Python, so a “print statement” is really just calling this print function.
As we saw earlier, we can pass parameters to this function call to give it inputs. For example, the print function takes parameters for the things to print.
print("To be or not to be")To be or not to be
Functions defined by Python are called built-in functions. print and range are two examples of built-in functions. Some functions can also return values, making them available for use outside the function.
If you want to define a function that takes parameters, you put variable names in between the () for each parameter you want the function to take. If you want the function to return a value, you use a return statement like the example below. To call the function, you need to actually use a function call! (Make sure that it’s not indented under your function header!)
def mean(a, b):
print('Calling mean with', a, b)
return (a + b) / 2
mean(1, 2) # Have to call it passing in two parameters!Calling mean with 1 2
1.5Strings¶
Strings are commonly used to represent text. In Python, str (pronounced “stir”) represents a string. We’ll refer to string and str interchangeably since we’re focusing on Python programming.
Earlier, we saw that str values are defined by surrounding text in matching quotes: either a ' or a ".
s1 = 'hello world'
s2 = "CSE 163 is fun!"
print(s1)
print(s2)hello world
CSE 163 is fun!
Strings provide lots of ways of accessing and transforming the text data to do almost anything you want! One of the most common things you want to do with strings is to combine them. In the following snippet, we use string concatenation to add on one string to the end of another.
s1 = 'hello world'
s2 = "CSE 163 is fun!"
print(s1 + s2)hello worldCSE 163 is fun!
The + operator in this context will work with two values of type str to create a new str that has the characters from the first followed by the second. Importantly, this does not modify either of s1 or s2, but rather creates a new string that has the same characters as both of them. In fact, strings are what we call immutable, meaning that you can’t change the characters of a particular string at all!
Take a second and think about what the following code snippet should be based on this description of string concatenation before running it... Okay! Let’s run it! Did it do what you expected?
s = 'hello world'
n = 163
print(s + n)---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[20], line 3
1 s = 'hello world'
2 n = 163
----> 3 print(s + n)
TypeError: can only concatenate str (not "int") to strAh, our first (intentional) error message. Python says that string concatenation is only defined between two values that are both of type str. To fix this, you have to explicitly turn the value 163 into a str! You could easily just change the code to wrap the value 163 in quotes so it is '163', but that only works under a very narrow set of circumstances. Instead, we will cast the number into a str using the str() function.
s = 'hello world'
n = 163
print(s + str(n))hello world163
Casting works for some other kinds of type conversions as well. Most commonly in this class, you might encounter int() or float() to turn a value into an integer, or a float, respectively.
String indexing¶
When we think of strings, we commonly think of them as a sequence of characters, where each character has an index in the string. For example, the string 'hello world' should really be thought of as a sequence of characters in the image shown below. Each character has its own spot in the sequence, and the spots are ordered starting at index 0 going up to the end of the string.
| h | e | l | l | o | w | o | r | l | d | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Python lets you access a character at a specific index by using this [] notation to index into the str itself. Consider the following code snippet and think about what it will print before you run it.
s = 'hello world'
print(s[0])
print(s[2])
print(s[10])h
l
d
Each of these goes into the string, and grabs and returns the character at the particular index.
Trying to get the last character is a very common operation, so it would be annoying to have to count by hand how many characters you need to go till the end. Thankfully, Python provides a way to find the number of characters, called the length, of the string. If you have a str named s, then using the built-in function len tells you the number of characters in it.
Let’s try to grab that last character using the len function in the following snippet:
s = 'hello world'
print('Length of string:', len(s))
print('Last character:', s[len(s)])Length of string: 11
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[23], line 3
1 s = 'hello world'
2 print('Length of string:', len(s))
----> 3 print('Last character:', s[len(s)])
IndexError: string index out of rangeUh-oh. Another error. The crash this time was because we went “out of bounds” of the string. What went wrong? If you look at the first line of output, it actually succeeded in printing out the length. For this string, the length is 11. So we then tried to access s[len(s)] which is the same as s[11]. See the problem here?
The last valid index of this string is 10. Even though there are 11 characters, the valid indices go from 0 to 10. This is precisely because the first index of the string starts at 0. To fix this, we need to ask for one index earlier in our code:
s = 'hello world'
print('Length of string:', len(s))
print('Last character:', s[len(s) - 1])Length of string: 11
Last character: d
One of the cool things about Python is that the language supports negative indexing. Pictorally, a string can also look like this:
| h | e | l | l | o | w | o | r | l | d | |
|---|---|---|---|---|---|---|---|---|---|---|
| -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
To access the very last index of the string now, we can write:
s = 'hello world'
print('Last character:', s[-1])Last character: d
Food for thought: When might negative indexing be helpful? When might positive indexing be helpful?
String Slicing¶
String indexing gets a single character from a string. How do we get multiple characters from a string? Python has a special syntax called slicing that enables patterned access to substrings: s[start:end]. Just like the range function, the start is inclusive and the end is exclusive.
s = 'hello world!'
print(s[2:7])llo w
The slice shown above means “all characters starting at index 2 and up to (not including) 7”. If you take this with the picture below, it can clarify why the cell above prints "llo w".
| h | e | l | l | o | w | o | r | l | d | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
To slice all the way from the beginning of a string to a specified (exclusive) end, don’t specify a start:
s = 'hello world!'
print(s[:7])hello w
To slice all the way to the end of a string from a specified (inclusive) start, don’t specify an end:
s = 'hello world!'
print(s[2:])llo world!
Like range, you can also add a step parameter to string slicing. What do you think will be the output below?
s = 'hello world!'
print(s[2:8:2])low
Looping over a String¶
There are two ways that we can loop through the characters in a string. The first is by using the range function to loop over string indices.
s = "hello world"
for i in range(len(s)):
print(s[i])h
e
l
l
o
w
o
r
l
d
Note that since range gives integer values, those integers are then used to access indices in the given string.
The other way is looping over the characters directly using a for loop. Recall that for loops iterate over a sequence, and a str is a sequence of the characters in that string!
s = "hello world"
for c in s:
print(c)h
e
l
l
o
w
o
r
l
d
String Functions¶
We have seen a simple function already that allows us to index into the string so it can return the character at that index. You can imagine this indexing as just one special function provided to us by the string (although the syntax looks different than the general notion we will show below).
For example, there is also a find function that you can call on a string s1, that returns the index of a given string s2 inside s1. For example, you could write the following:
source = 'I really like dogs'
target = 'll'
print(source.find(target))5
In this case, it prints 5 because the first occurrence of the substring 'll' in source appears at index 5. Notice that we call a function on the string source by saying source.<function_name>(...) where ... are the parameters we need to pass to that function. If we were to call target.find(source) it would be looking for source inside target and would instead return -1 (as defined in the documentation for find). You can try this in the code cell below.
source = 'I really like dogs'
target = 'll'
print(target.find(source))-1
Here are some useful functions for str.
s.lower()returns a new string that is the lowercase version ofss.upper()returns a new string that is the uppercase version ofss.find(t)returns the index of the first occurrence oftins. If not found, returns-1.s.strip()returns a new string that has all the leading and trailing whitespace removed.lstrip()andrstrip()remove only left whitespace or right whitespace respectively.)s.split(delim)returns a list consisting of the parts ofssplit up according to thedelim.s.join(strings)returns a single string consisting of the given strings with the stringsinserted between each string.
Because strings are immutable, all string functions that return a str return new strings, rather than modifying the original string s.
Let’s look more closely at the last two functions in this list.
Suppose we had a string that contained a series of values separated by the , character, but we want to print the string out in all upper-case letters separated by | characters. We put print statements in between to make the sequence of transformations clearer and to provide comments explaining the step.
data = 'Mercury,Venus,Earth,Mars'
print('Original:', data)
# Convert the data to have all upper-case letters
data = data.upper()
print('After upper:', data)
# Split the data up by a ','. Returns a list of parts.
# We will describe lists later.
parts = data.split(',')
print('After split:', parts)
# Put those parts back together separated by a '|'
data = '|'.join(parts)
print('After join:', data)Original: Mercury,Venus,Earth,Mars
After upper: MERCURY,VENUS,EARTH,MARS
After split: ['MERCURY', 'VENUS', 'EARTH', 'MARS']
After join: MERCURY|VENUS|EARTH|MARS
Note that the join function doesn’t add whitespace! If you want spaces between the joined strings, make sure to add it in the original string you join with:
parts = 'Mercury,Venus,Earth,Mars'.split()
data2 = ' and '.join(parts)
print('After a different join:', data2)After a different join: Mercury,Venus,Earth,Mars
⏸️ Pause and 🧠 Think¶
Take a moment to review the following concepts and reflect on your own understanding. A good temperature check for your understanding is asking yourself whether you might be able to explain these concepts to a friend outside of this class.
Here’s what we covered in this lesson:
printstatementsVariables and variable assignment
Variable types (strings,
float,int,bool)Arithmetic operators (
+,-,/,*,//,%,**)Logical operators (
and,or,not,<,>,<=>=,==,!=)whileloopsforloopsrangeConditionals
Functions
strindexingstrslicingLooping over a
strlenfunctionstrfunctions
Practice Problems¶
We’ll now spend some time working on countdown.py, fibonacci.py, switch_pairs.py in small groups and review the solutions together afterwards.
Expressions¶
What is the output of the following expression?
x = 2.4
y = 1.2
x = x / y
y = 5
print(x ** 2 <= y)True
What is the output of the following expression?
a = 3
b = 1
while (a % 2 != 0):
b += 1
a += a ** b
print(a)12
Slices¶
For all the following questions, assume we are using the string
s = 'I have eaten the plums that were in the icebox'What are the values of the following slices? (You can add code cells to verify your answers!)
s[3:10]s[s.find('p'):]s[:s.find('t'):3]
s = 'I have eaten the plums that were in the icebox'
print(s[3:10])
print(s[s.find('p'):])
print(s[:s.find('t'):3])ave eat
plums that were in the icebox
Ia
Countdown¶
For this practice problem, refer to countdown.py.
First, let’s write a program in main that counts down from one minute decrementing by 10 seconds. Use a for loop. The output of the program should be the following:
One minute countdown
60
50
40
30
20
10
0
Done!The lines of numbers should all be produced by your for loop while the first and last lines will appear outside the loop since they only happen once. Think carefully about the inputs to range—the arguments can be negative!
Once you’ve written this program in main, let’s reuse this logic in a function. We’ll write the function countdown that takes a starting number of int seconds and starts the countdown from there instead (still decrementing by 10s).
The format of the output will be slightly different to accommodate this starting point: if the sequence does not exactly count down to 0 (e.g., starting from 15), then 0 will not be printed. If the starting number of seconds is less than 0, it should instead print "Start must be non-negative!"
Here are four example calls to the function with their corresponding outputs. print statements are included for spacing between different calls to countdown.
countdown(60)
print()
countdown(15)
print()
countdown(-4)
print()
countdown(0)Output:
60 second countdown
60
50
40
30
20
10
0
Done!
15 second countdown
15
5
Done!
Start must be non-negative!
0 second countdown
0
Done!Fibonacci¶
For this practice problem, refer to fibonacci.py.
The Fibonacci Sequence is the following sequence of numbers: 1, 1, 2, 3, 5, 8, 13, ... The sequence starts with 1 and 1. The next number is the sum of the previous two.
Here, we will write a function fibonacci to compute the first Fibonacci number larger than a given value n. As an example, if we call fibonacci(0), then the output would be 1, since 1 is the first Fibonacci number greater than 0.
Our function should return the first Fibonacci number in the sequence that exceeds the given value n. Here are more examples of output, where the expected output is written as an in-line comment next to the print and function call:
print(fibonacci(0)) # 1
print(fibonacci(1)) # 2
print(fibonacci(2)) # 3Hint: Create two variables representing the curr and prev Fibonacci numbers. The next number is the sum of curr and prev.
switch_pairs¶
For this problem, refer to the starter code in switch_pairs.py.
Write a function named switch_pairs that accepts a string as a parameter and returns that string with each pair of neighboring letters reversed. If the string has an odd number of letters, the last letter should not be modified. For example, the call switch_pairs("example") should return "xemalpe" and the call switch_pairs("hello there") should return "ehll ohtree".
Note that this function returns a string and does not print anything. You should not make a list to solve this problem. You should solve it by building up a str.