The content for this lesson is adapted from material by Hunter Schafer and by Kevin Lin.
Objectives¶
In this lesson, we’ll introduce strings, lists, and None in Python. We’ll also learn the principles of documenting code. By the end of this lesson, students will be able to:
- Evaluate expressions involving strings, string slicing, and lists.
- Apply
stroperations and slicing to compute a new string representing the desired text. - Apply
listoperations to store, retrieve, and modify values in a list.
Setting up¶
Refer to the instructions on our Software page for how to install Anaconda, VS Code, and the CSE 163 environment!
Note!
The linked page is an older version of the setup instructions that we have temporarily linked for the preliminary launch of the course website. The instructions will mostly work. However, note that you should install Python 3.13 instead of 3.9 and use the environment file here instead of the one that is on the page. All other steps and downloads can be followed as they appear on the software setup instructions.
To follow along with the code examples in this lesson, please download the files here:
Strings¶
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.
In Lesson 1, 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)
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)
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)
Ah, 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))
Casting
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])
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)])
Uh-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])
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])
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])
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])
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:])
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])
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])
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)
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))
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))
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.
Immutability
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 in this reading.
parts = data.split(',')
print('After split:', parts)
# Put those parts back together separated by a '|'
data = '|'.join(parts)
print('After join:', data)
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:
data2 = ' and '.join(parts)
print('After a different join:', data2)
Lists¶
The s.split(delim) function defined in the list above introduced another data type called a list. Whereas a string is an indexed sequence of characters, a list is an indexed sequence that can store values of any type.
List Operations¶
The great thing about lists in Python, is that they share a lot of the same syntax for operations as strings. The following snippet shows you all of the string syntaxes we learned this lesson also applies to lists.
l = ['dog', 'says', 'woof']
# Length
print(len(l)) # 3
# Indexing
print(l[1]) # 'says'
print(l[len(l) - 1]) # 'woof'
# Slicing
print(l[1:3]) # ['says', 'woof']
print(l[:1]) # ['dog']
# Looping
for i in range(len(l)):
print(l[i])
for word in l:
print(word)
Some key differences/things to point out:
First, to specify the values of a list, the syntax uses square brackets around a comma-separated list of values (of any type).
l1 = ['I', 'love', 'dogs']
l2 = [7, 8, 9]
l3 = ['I', 3, 'dogs']
Second, because this is now a list, not a str, the values returned by indexing are different. By indexing into index 0 of l1, it returns the value that’s in that index (in this case, the str 'I'). Similarly, when you slice into a list you get a list back (although it’s shorter).
Food for thought: How might you access the letter 's' from 'dogs' in l3?
List Assignment¶
One way lists are able to do a bit more than strings comes from the fact that you are allowed to change the contents of a list by assigning into it. We say that lists are therefore mutable. Just like you can use an index to get a value out of a list, you can use an index to set a value at a particular spot.
l1 = ['I', 'love', 'dogs']
print(l1)
l1[0] = 'You'
print(l1)
Documentation Matters!¶
Documentation for a function communicates information about a program (such as its purpose and how to use it) to other programmers. Reading code can be hard so we want to provide some instructions people can understand. Code without documentation requires anyone trying to use your code to try and recreate what you were thinking.
You can always use the # to leave a note in your code, but we are specifically going to talk about a special notion in Python called a docstring (“doc-string”) that lets you add special documentation for a function. To do this, you use this special triple-quote string (i.e. """ documentation """) as the first lines of the function.
def mean(a, b):
"""
Returns the average of a and b
"""
return (a + b) / 2
Everything that goes inside these triple-quotes now becomes the documentation for a function.
Why does it help to specify this special docstring? Well Python has built-in tools to help you view the doc-strings for any function!
Python provides a help function that lets you see the documentation for any function! For long documentation, it brings up a special viewer where you can move up/down with the keys j/k and you can quit with the key q.
help(print)
To see how this works with code you write, suppose I wrote the function with the following docstring and asked for help on it.
def function_with_good_comment():
"""
This function does some really cool stuff
"""
print('Friends of the ABC')
help(function_with_good_comment)
Food for thought: What would happen if you put everything after a # comment instead? Try it and see!
def function_with_good_comment():
# This function does some really cool stuff
print('Friends of the ABC')
help(function_with_good_comment)
Every function you write should include a descriptive docstring comment! The comment should include information for the following:
- The input type of the function and what the input means
- The return type of the function and what the return means
- The expected behavior of the function
- Any special case (return
None, empty input, etc.)
Check out our Code Quality Guide for details!
None¶
Python has a special value in the language called None to represent the absence of a value. We sometimes use None to represent a result of a failed or invalid computation. (The other common way to represent a failed or invalid computation is to crash the program by raising an error.)
For example, suppose we had the following increment function that we decided should only increment positive values. You could write code to throw an error if it was given a negative value, but it’s very common in Python to instead return None.
def increment(x):
"""
Returns the value of incrementing a non-negative value x by 1.
If x is negative, returns None.
"""
if x < 0:
return None
else:
return x + 1
print(increment(3))
print(increment(-3))
None should not be confused with the value 0! 0 is a valid number in Python (i.e. 1 + 0 is well defined to be 1) while None is the absence of a number all together! If you were to run 1 + None it would cause an error since it doesn’t make sense to add 1 to something that is entirely missing!
A TypeError or AttributeError can occur when manipulating a None value. Try modifying the code snippet to display a TypeError by adding a number to None.
Checking for None¶
A very common task in Python is to check if a particular value is None or not. Check for None using the is operator rather than the == operator.
def increment(x):
"""
Returns the value of incrementing a non-negative value x by 1.
If x is negative, returns None.
"""
if x < 0:
return None
else:
return x + 1
x = increment(-1)
if x is None: # checking for None
print('Failed')
else:
print(x)
Alternatively,
if x is not None:
print(x)
else:
print('Failed')
⏸️ 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:
strindexingstrslicing- Looping over a
str lenfunctionstrfunctionslistfunctions- Documenting functions
Here are some other guiding exercises and questions to help you reflect on what you’ve seen so far:
- In your own words, write a few sentences summarizing what you learned in this lesson.
- What did you find challenging in this lesson? Come up with some questions you might ask your peers or the course staff to help you better understand that concept.
- What was familiar about what you saw in this lesson? How might you relate it to things you have learned before?
- Throughout the lesson, there were a few Food for thought questions. Try exploring one or more of them and see what you find.
In-Class¶
When you come to class, we will work together on evaluating Pythonic expressions, and completing switch_pairs.py and count_votes.py. Make sure that you have a way of editing and running these files!
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?
s[3:10]s[s.find('p'):]s[:s.find('t'):3]
Mystery¶
What are the values of the elements in list a1 after the following code executes?
def mystery(a1, a2):
for i in range(len(a1)):
a1[i] += a2[len(a2) - i - 1]
a1 = [1, 3, 5, 7, 9]
a2 = [1, 4, 9, 16, 25]
mystery(a1, a2)
(i.e., what are the values of a1[0], a1[1], a1[2], a1[3], and a1[4]?)
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.
count_votes¶
For this problem, refer to the starter code in count_votes.py.
Write a function count_votes that takes a list of numbers indicating votes for candidates 0, 1, or 2 and returns a new list of length 3 showing how many counts each candidate got. For example:
votes = [1, 0, 1, 1, 2, 0]
result = count_votes(votes)
print(result) # [2, 3, 1]
The returned list has a 2 at index 0 because a 0 appeared twice in the votes.
Canvas Quiz¶
All done with the lesson? Complete the Canvas Quiz linked here!