Lab 02 - Functions, While loops, and File I/O
Due by 11:59pm on January 16, 2025
In this lab you’ll continue to practice your skills with fundamental Python concepts. Specifically, you’ll be looking at file I/O, functions, while loops, and lists.
As always, you should read the entire assignment before starting on any part of it.
A note on testingThroughout the lab questions you’ll be prompted to run the tests on your code. This is a habit you should get into on all of your assignments and we’ll prompt you to do it in the early ones. If you are testing as you go (as you should be), there will be many errors reported for the parts of the assignments that you haven’t implemented yet. We sometimes provide commands to just test a portion of your code reducing the errors but not always. This is okay, you should just be looking for errors on the parts you have done and eliminate them before moving on.
Starter Files
Once you’ve read through the entire assignment, come back here and download lab02.zip. Inside the archive, you will find starter files for the questions in this lab.
Topics
You can skip over this Topics section if you and everyone in your group feels confident in your understanding as this is just a review of concepts from lecture, but if you run in to questions later on, this should be the first place you look for help.
If not everyone is confident in their understanding of the topics, you should discuss with one another to help them understand. Teaching others is one of the best way to cement your own understanding. Take advantage of the opportunity when it arises.
While Loops
You can review the syntax of while
loops in Section 1.5.5 of Composing Programs. It will also be reviewed down below.
A while
loop requires an expression that evaluates to either a truthy or falsey value. As long as that expression evaluates to truthy, the code in the while
loop’s body will continue to execute. Because of this, you must be careful not to put a condition that will never become falsey or else the while
loop will run forever! Here’s the syntax:
while <conditional expression>:
<suite of statements>
Here’s an example of a while
loop that will iterate (execute the loop’s body) 5 times using a variable called i
.
i = 0
while i < 5:
print(i)
i += 1 # equivalent to `i = i + 1`
Lists
A list is a data structure that can store multiple elements. Each element can be any type, even a list itself. We write a list as a comma-separated list of expressions in square brackets:
>>> list_of_ints = [1, 2, 3, 4]
>>> list_of_bools = [True, True, False, False]
>>> nested_lists = [1, [2, 3], [4, [5]]]
Each element in the list has an index, with the index of the first element starting at 0
. We say that lists are therefore “zero-indexed.”
With list indexing, we can specify the index of the element we want to retrieve. A negative index represents starting from the end of the list, where the negative index -i
is equivalent to the positive index len(lst)-i
.
>>> lst = [6, 5, 4, 3, 2, 1, 0]
>>> lst[0]
6
>>> lst[3]
3
>>> lst[-1] # Same as lst[6]
0
>>> lst[-2]
1
To check if an element is within a list, use Python’s in
keyword. For example,
>>> numbers = [2, 3, 4, 5]
>>> 1 in numbers
False
>>> n = 2
>>> n in numbers
True
To find the number of elements in a list, Python comes with a prebuilt function len(<list>)
.
>>> numbers = [0, 1, 2, 3, 4]
>>> len(numbers)
5
With the len()
function, we can iterate through each element of a list using an index.
>>> numbers = [10, 9, 8, 7, 6]
>>> index = 0
>>> while index < len(numbers):
... print(numbers[index])
... index += 1
...
10
9
8
7
6
When accessing an element through a list using an index, we are given a reference to the element and are allowed to manipulate it within the list.
>>> numbers = [10, 9, 8]
>>> numbers[0] = 5
>>> numbers
[5, 9, 8]
Additional List Methods
Some of these may come in very handy when coding, but they may not be necessarily required in this class.
Adding to a List
.append(<elem>)
- Adds an element to the back of a list. ReturnsNone
.
>>> nums = [0, 1, 2]
>>> nums.append(3)
>>> nums
[0, 1, 2, 3]
.extend(<list>)
- Extends a list given another list. ReturnsNone
.
>>> nums = [0, 1, 2]
>>> new_nums = [3, 4, 5]
>>> nums.extend(new_nums)
>>> nums
[0, 1, 2, 3, 4, 5]
.insert(<index>, <elem>)
- Inserts an element at a given index and returnsNone
. If there is already an element at the index, it will be pushed back to make space for the new element.
>>> forbidden_toppings = ["pineapple", "tomato"]
>>> forbidden_toppings.insert(1, "mushroom")
>>> forbidden_toppings
["pineapple", "mushroom", "tomato"]
Removing from a List
.pop(<index>)
- Removes the element at the provided index. If an index is not provided, the index will default to the last index in the list (len(<list>) - 1
) and remove the last element..pop()
returns the removed element.
>>> nums = [0, 1, 2, 3]
>>> three = nums.pop()
>>> nums
[0, 1, 2]
>>> three
3
>>> nums.pop(1)
1
>>> nums
[0, 2]
.remove(<elem>)
- Removes the first occurrence of the element provided. ReturnsNone
.
>>> food = ['potato', 'tomato', 'tomato', 'pineapple']
>>> food.remove('tomato')
>>> food
['potato', 'tomato', 'pineapple']
It is important to notice that none of these methods return a modified list but instead modify the list provided directly.
You can also refer to w3schools for more detailed information and more methods like sort()
Functions
If we want to execute a series of statements over and over, we can abstract them away into a function to avoid repeating code. In fact, it is a good programming practice that whenever you find yourself writing the same code a second time, you should move that code to a function and call the function where you would have written the code.
For example, let’s say we want to know the results of multiplying the numbers 1-3 by 3 and then adding 2 to it. Here’s one way to do it:
>>> 1 * 3 + 2
5
>>> 2 * 3 + 2
8
>>> 3 * 3 + 2
11
If we wanted to do this with a larger set of numbers, that’d be a lot of repeated code! Let’s write a function to capture this operation given any input number.
def foo(x):
return x * 3 + 2
This function, called foo
, takes in a single argument and will return the result of multiplying that argument by 3 and adding 2.
Now we can call this function whenever we want this operation to be done:
>>> foo(1)
5
>>> foo(2)
8
>>> foo(1000)
3002
Applying a function to some arguments is done with a call expression.
Call Expressions
A call expression applies a function, which may or may not accept arguments. The call expression evaluates to the function’s return value.
The syntax of a function call:
add ( 2 , 3 )
| | |
operator operand operand
Every call expression requires a set of parentheses delimiting its comma-separated operands.
To evaluate a function call:
- Evaluate the operator, and then the operands (from left to right).
- Apply the operator to the operands (the values of the operands).
If an operand is a nested call expression, then these two steps are applied to that inner operand first in order to evaluate the outer operand.
return
and print
Most functions that you define will contain a return
statement. The return
statement will give the result of some computation back to the caller of the function and exit the function. For example, the function square
below takes in a number x
and returns its square.
def square(x):
"""
>>> square(4)
16
"""
return x * x
When Python executes a return
statement, the function terminates immediately. If Python reaches the end of the function body without executing a return
statement, it will automatically return None
.
In contrast, the print
function is used to display values in the terminal. This can lead to some confusion between print
and return
because calling a function in the Python interpreter will print out the function’s return value.
However, unlike a return
statement, when Python evaluates a print
expression, the function does not terminate immediately.
def what_prints():
print('Hello World!')
return 'Exiting this function.'
print('CS111 is awesome!')
>>> what_prints()
Hello World!
'Exiting this function.'
NoteNotice that
return
will preserve the quotes when the return value is displayed by the interpreter.
File I/O
Whenever we perform an operation with a program, unless we save the output of that operation to a file, the information is lost when we close the program. In this class, we will primarily focus on writing to and reading from text files.
Python already has a built-in way to create, write to, and read files.
To work with a file, we need to open it by using the open
function and provide as arguments the filename
and the mode
we want to use. There are several different modes for opening a file such as write mode, read mode, or append mode.
Opening and Closing a File
There are three steps when working with files:
- Open the file in a mode.
- Do stuff with the file.
- Close the file.
To open and close a file, you could use this syntax:
file = open(<file_name>, <mode>)
# code that does stuff with the file
file.close()
WarningPlease don’t do it that way in your code unless you need to. It’s very common to forget to close a file, the problems it causes are not obvious, and it will break the autograder. Instead, make the computer do that work for you.
Do this instead:
with open(<file_name>, <mode>) as file:
# code that does stuff with the file
# the file is closed out here
All the code that is indented after the with open
, will have access to the file for reading or writing. Once Python is finished executing the indented code suite, then the file automatically closes.
file_name
and mode
have to be strings. Mode specifically has to be either
"w"
- write mode"r"
- read mode"a"
- append mode- or other modes supported by python
NoteYou only really need to use the first method (using
open()
andclose()
) if you have a bunch of files open simultaneously that you are reading and writing to at the same time. You could still use thewith open()
syntax to do that, but you have to nest all thewith
statements and if you have a lot of files, that’s a lot of indentation.
ImportantIf you don’t use the
with open()
method, make sure to close your file. Not doing so will waste space and break the autograder.
Reading From a File
If you do not provide a mode when opening a file, python will automatically open it in read mode.
There are multiple ways to read from a file. The following are a few methods to do so:
.read()
- returns a string containing all the contents of the file including newlines\n
and tabs\t
..readline()
- returns a string with the contents of the first line including newlines\n
and tabs\t
. Once it returns the contents of the first line, another call toreadline()
will return the contents of the second line, and so on and so forth until the final line..readlines()
- returns a list of strings where each string is a line. Newlines and tabs are explicitly shown in each string.
Remember, since these are methods
, these follow the format of <file>.read()
. The differences between methods and functions will become more apparent in the Classes lectures later in the semester.
Writing to a File
If the file does not exist and you are opening the file in write mode, then python will automatically create the file. If the file already has content, opening it in write mode will erase everything and override it with what you choose to write to it.
There are two ways to write to a file in python:
.write(<line_content>)
- writes the exact contents given in a string..writelines(<list_of_line_content)
- writes each string in the list into the file.
.write()
may not work as you might think. If we were to use the following code:
with open("cs_is_cool.txt", 'w') as file:
file.write("Hello World")
file.write(":D")
Right now, our cs_is_cool.txt
looks like this:
Hello World:D
By default, the write()
method does not put a new line at the end of the string; we have to do that ourselves by putting "\n"
(backslash-n) at the end of the string.
file.write("Hello World\n")
file.write(":D")
Now, with the addition of the "\n"
, our cs_is_cool.txt
looks like this:
Hello World
:D
Additionally, we can use the writelines()
method which takes in a list of strings and writes each of them into the file:
lines = ["Hello World\n", ":D"]
file.writelines(lines)
Required Questions
Q1: WWPD: Lists
What would Python display for each of these? (If you are completely stumped on what the code is doing, type the code in to the interpreter and see what happens and if you were correct. If not, spend some time figuring out what was wrong.)
>>> a = [1, 5, 4, [2, 3], 3]
>>> print(a[0], a[-1])
______
>>> len(a)
______
>>> 2 in a
______
>>> 2 in a[3]
______
>>> a[3][0]
______
>>> a[3][0] += 1
>>> a
______
>>> a[3][2]
______
Discuss the following with your group:
- Did the output of any of these statements surprise you or gave results you didn’t expect? Which ones?
- Do you understand why the output you saw occurred? If you don’t completely understand, ask someone in your group to explain it. Try explaining one of the results you do understand to someone else.
Q2: WWPD: Control
Use the following “What Would Python Display?” questions to test your knowledge on while statements:
HintMake sure your
while
loop conditions eventually evaluate to a false value, or they’ll never stop! Typing [Ctrl]+[C] will stop infinite loops in the interpreter.
>>> n = 3
>>> while n >= 0:
... n -= 1
... print(n)
______
______
______
______
>>> positive = 13
>>> while positive:
... print(f"Is {positive} positive?")
... positive -= 3
______
Discuss the following with your group:
- In the first code snippet, what was the last value printed? Do you understand why? Walk through the code flow with someone explaining what is happening
- In the second, why didn’t the loop terminate on its own? How would you modify the code to make it stop when the
positive
variable becomes negative?
Q3: Even Weighted
Implement the function even_weighted
that takes a list s
and returns a new list that keeps only the even-indexed elements of s
and multiplies those elements by their index in the original list.
def even_weighted(s):
"""
>>> x = [1, 2, 3, 4, 5, 6]
>>> even_weighted(x)
[0, 6, 20]
"""
"*** YOUR CODE HERE ***"
HintRecall that a even number is a number that is evenly divisible by 2 and therefore has 0 remainder. We can use the modulo operator
%
to find the remainder and ensure it is 0:number % 2 == 0
.
Test your code
Start with the doctests:
python3 -m doctest lab02.py
You should see zero errors for even_weighted
. If there are errors for this function, fix them and keep testing until they are gone.
Next run the pytests:
pytest -vv test_lab02.py::test_even_weighted_1
pytest -vv test_lab02.py::test_even_weighted_2
Again, you should see zero errors for tests that include even_weighted
in their name. If there are errors for this function, fix them and keep testing until they are gone.
Q4: Couple
Implement the function couple()
, which takes in two lists and returns a list that contains lists with i-th elements of two sequences coupled together. You can assume the lengths of two sequences are the same. Try using a list comprehension.
def couple(s, t):
"""Return a list of two-element lists in which the i-th element is [s[i], t[i]].
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> couple(a, b)
[[1, 4], [2, 5], [3, 6]]
>>> c = ['c', 6]
>>> d = ['s', '1']
>>> couple(c, d)
[['c', 's'], [6, '1']]
"""
assert len(s) == len(t)
"*** YOUR CODE HERE ***"
Test your code
Start with the doctests:
python3 -m doctest lab02.py
You should see zero errors for couple
. If there are errors for this function, fix them and keep testing until they are gone.
Next run the pytests:
pytest -vv test_lab02.py::test_couple_1
pytest -vv test_lab02.py::test_couple_2
Again, you should see zero errors for tests that include couple
in their name. If there are errors for this function, fix them and keep testing until they are gone.
Q5: WWPD: Reading from a file
Create a file called cs_is_cool.txt
and type something into it that spans a few lines. Be sure to save the file. Make sure you are in the same directory as the cs_is_cool.txt
file create a demo.py
file. Copy the following code to that file. Before you run the code, discuss what you think will be printed out. Now run it.
# if a mode is not provided, open will automatically default to read mode
with open("cs_is_cool.txt") as input_file:
file_lines = input_file.readlines()
print(file_lines)
Discuss with your group:
- What shows up? Try using
.readline()
and.read()
instead. - Do you understand what is happening with each function? Can you explain the results to someone else?
Q6: Copy File
Implement the copy_file
function. It takes two strings, input_filename
and output_filename
. It opens the two files, reads the file specified by the input_filename
line by line, and for each line it prints and writes to the file specified by the output_filename
the line with the line number and colon prepended to it. This function does not return anything.
def copy_file(input_filename, output_filename):
"""Print each line from input with the line number and a colon prepended,
then write that line to the output file.
>>> copy_file('text.txt', 'output.txt')
1: They say you should never eat dirt.
2: It's not nearly as good as an onion.
3: It's not as good as the CS pun on my shirt.
"""
"*** YOUR CODE HERE ***"
HintWhen reading from a file, python will interpret the new lines as a literal
"\n"
. As a result, when printing a line, you will have an extra empty new line because theprint()
function already appends a newline to the end of the content you provide. To mitigate this, you can use useprint(line, end="")
. By default,end="\n"
and changing this removes the extra empty newline. Another solution is to use<string>.strip()
on each of the lines from the file you are reading from. The.strip()
method will removing any leading or trailing whitespace from a string, like newlines.
Test your code
Start with the doctests:
python3 -m doctest lab02.py
You should see zero errors for copy_file
. If there are errors for this function, fix them and keep testing until they are gone.
Next run the pytests:
pytest -vv test_lab02.py::test_copy_file
Again, you should see zero errors for tests that include copy_file
in their name. If there are errors for this function, fix them and keep testing until they are gone.
Submit
Once the pytests are all passing, your assignment is complete and ready to submit. Submit your lab02.py
file to Gradescope to receive credit. Submissions will be in Canvas.
Extra Practice
Q7: Factors List
Write factors_list
, which takes a number n
and returns a list of its factors in ascending order.
def factors_list(n):
"""Return a list containing all the numbers that divide `n` evenly, except
for the number itself. Make sure the list is in ascending order.
>>> factors_list(6)
[1, 2, 3]
>>> factors_list(8)
[1, 2, 4]
>>> factors_list(28)
[1, 2, 4, 7, 14]
"""
all_factors = []
"*** YOUR CODE HERE ***"