names = ["alice", "bob", "charlie", "dave"]Session 3
- For Loop
- Lists
- Reading Files
- List Comprehensions
- Iteration patterns
For Loop
for name in names:
print("Hello", name)Hello alice
Hello bob
Hello charlie
Hello dave
Lists
x = ["a", "b", "c", "d"]x['a', 'b', 'c', 'd']
x[0]'a'
x[1]'b'
How to get the last element?
x[len(x)-1]'d'
x[-1]'d'
+----+----+----+----+
| a | b | c | d |
+----+----+----+----+
| 0 | 1 | 2 | 3 | --> regular index
+----+----+----+----+
| -4 | -3 | -2 | -1 | <-- negative index
+----+----+----+----+
def get_last_word(sentence):
return sentence.split()[-1]get_last_word("one two three")'three'
Modifying & Growing Lists
x = ["a", "b", "c", "d"]x[0] = "aa"x['aa', 'b', 'c', 'd']
x.append("e")x['aa', 'b', 'c', 'd', 'e']
Please note that the append method modifies the list in place and doesn’t return anything.
x = ['a', 'b', 'c', 'd']x = x.append('e')print(x)None
It is None because the append method doesn’t return the list back.
x = [1, 2, 3]
y = x
x.append(4)
print(x)
print(y)[1, 2, 3, 4]
[1, 2, 3, 4]
x = [1, 2, 3]
y = x
x = [4, 5, 6]
print(x)
print(y)[4, 5, 6]
[1, 2, 3]
There are two different kind of modifications.
- The variable is getting reassigned to a new value (rebinding)
- The value is getting modified (mutation)
x = 1
y = x
x = 2 # rebinding
print(x, y)2 1
x = [1, 2, 3]
y = x
x.append(4) # mutation
print(x)
print(y)[1, 2, 3, 4]
[1, 2, 3, 4]
List Slicing
x = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight"]x[0:2] # from index 0 to index 2 (end is not included)['zero', 'one']
x[:2] # up to index 2['zero', 'one']
x[2:] # from index 2 onwards['two', 'three', 'four', 'five', 'six', 'seven', 'eight']
x[1:6] # from index 1 to 6['one', 'two', 'three', 'four', 'five']
x[1:6:2] # from index 1 to 6, take every second element['one', 'three', 'five']
What about all elements, except the last one?
x[:-1]['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven']
How to get the last two elements?
x[-2:]['seven', 'eight']
What if we want the list in the reverse order?
x[::-1]['eight', 'seven', 'six', 'five', 'four', 'three', 'two', 'one', 'zero']
x[:] # copy of x['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight']
list(x) # make a new list with all elements of x['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight']
Example: echo.py
In the previous classes, we have written an echo.py that printed just the first argument. Can we improve it to print all arguments, just like the echo command.
!echo hello worldhello world
!echo 1 2 3 41 2 3 4
%%file echo.py
import sys
args = sys.argv[1:]
print(" ".join(args))Overwriting echo.py
!python echo.py hello worldhello world
!python echo.py 1 2 31 2 3
Reading Files
!ls filesabc empty.txt numbers words2.txt zen-of-python.txt
bumper-stickers.txt five.txt ten.txt words.txt
%%file three.txt
one
two
threeWriting three.txt
open("three.txt").read()'one\ntwo\nthree\n'
open("three.txt").readlines()['one\n', 'two\n', 'three\n']
print(open("three.txt").read())one
two
three
for line in open("three.txt").readlines():
print(line)one
two
three
Do you know why there is gap between the lines?
The line has a new line character at the end and print is adding one more.
We can fix it either by striping the new line from the line or asking print to not add a newline.
for line in open("three.txt").readlines():
print(line.strip("\n"))one
two
three
for line in open("three.txt").readlines():
print(line, end="")one
two
three
Problem: Head
%load_problem headWrite a program head.py to print the first 5 lines of a file.
$ python head.py files/ten.txt
1
2
3
4
5
You can verify your solution using:
%verify_problem head
%%file head.py
# your code here
print(1)
Writing head.py
!python head.py files/ten.txt1
!head -5 files/ten.txt1
2
3
4
5
filename = "ten.txt"for line in filename:
print(line)t
e
n
.
t
x
t
for c in "hello":
print(c)h
e
l
l
o
Solution:
%%file head.py
import sys
filename = sys.argv[1]
lines = open(filename).readlines()
lines = lines[:5] # take first 5 lines
for line in lines:
print(line, end="")Overwriting head.py
!python head.py files/ten.txt1
2
3
4
5
%verify_problem head✓ python head.py /opt/files/ten.txt
✓ python head.py /opt/files/zen-of-python.txt
✓ python head.py /opt/files/words.txt
✓ python head.py /opt/files/empty.txt
✓ python head.py /opt/files/leading-space.txt
🎉 Congratulations! You have successfully solved problem head!!
List Comprehensions
Computes squares of a list of numbers.
def squares(numbers):
result = []
for n in numbers:
result.append(n*n)
return resultsquares([1, 2, 3, 4, 5])[1, 4, 9, 16, 25]
numbers = [1, 2, 3, 4, 5][n*n for n in numbers][1, 4, 9, 16, 25]
result = [n*n for n in numbers]result[1, 4, 9, 16, 25]
[n*n for n in numbers if n%2 == 0][4, 16]
# compute the sum of squares of all even numbers below one million
sum([n*n for n in range(1000000) if n%2 == 0])166666166667000000
Let’s unpack them a bit.
result = [expr for a_var in a_list]
result = [expr for a_var in a_list if some_condition]That is equivalant to:
result = []
for a_var in a_list:
result.append(expr)
result = []
for a_var in a_list:
if some_condition:
result.append(expr)Find all python files in the current directory.
os.listdir(".") # all files['index.qmd',
'assignments.qmd',
'assignment-01.qmd',
'_quarto.yml',
'_book',
'course.qmd',
'references.qmd',
'.ipynb_checkpoints',
'three.txt',
'head.py',
'date.py',
'args.py',
'styles.css',
'echo.py',
'session3.ipynb',
'syllabus.qmd',
'countdown.py',
'session2.ipynb',
'square.py',
'.quarto',
'lab.qmd',
'files',
'script.js',
'session1.ipynb.invalid',
'schedule.qmd',
'session1.ipynb',
'hello.py',
'live-notes.qmd',
'schedule.ipynb',
'references.bib']
[f for f in os.listdir(".") if f.endswith(".py")]['head.py',
'date.py',
'args.py',
'echo.py',
'countdown.py',
'square.py',
'hello.py']
os.path.getsize("head.py")152
!ls -l head.py-rw-r--r-- 1 jupyter-anand jupyter-anand 152 Oct 5 04:50 head.py
What if we want to find the size of all the python files in the current directory.
[os.path.getsize(f) for f in os.listdir(".") if f.endswith(".py")][152, 35, 28, 55, 12, 62, 23]
sum([os.path.getsize(f) for f in os.listdir(".") if f.endswith(".py")])367
Problem: Evens
%load_problem evensWrite a function evens that takes a list of numbers as argument and returns a new list containing only the even numbers out of it.
>>> evens([1, 2, 3, 4, 5, 6])
[2, 4, 6]
You can verify your solution using:
%verify_problem evens
# your code here
Problem: Sum of Arguments
%load_problem sum-of-argumentsWrite a program sum.py that takes one or more numbers as command-line argument and prints their sum.
$ python sum.py 1 2 3 4 5
15
You can verify your solution using:
%verify_problem sum-of-arguments
%%file sum.py
import sys
args = sys.argv[1:]
numbers = [int(a) for a in args]
print(sum(numbers))Overwriting sum.py
!python sum.py 1 2 3 4 515
Iteration Patterns
Iterating over a list
x = ['a', 'b', 'c', 'd']for a in x:
print(a, a.upper())a A
b B
c C
d D
[a.upper() for a in x]['A', 'B', 'C', 'D']
[(a, a.upper()) for a in x][('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')]
Iterating over a sequence of numbers
for i in range(5):
print(i*i)0
1
4
9
16
[i*i for i in range(5)][0, 1, 4, 9, 16]
Iterating over two lists together
Let’s say we have names of students and their scores in two seperate lists. How can we print name and score for each one.
names = ["a", "b", "c", "d"]
scores = [10, 20, 30, 40]for i in range(len(names)):
print(names[i], scores[i])a 10
b 20
c 30
d 40
This is correct, but not Pythonic!
If you are ever accessing elements of a list in a loop using index, you are probably doing it the wrong way.
zip(names, scores)<zip at 0x7f77e56d2280>
You can’t see it becasue it is iterable object. We can convert it to a list to see what is inside.
list(zip(names, scores))[('a', 10), ('b', 20), ('c', 30), ('d', 40)]
for name, score in zip(names, scores):
print(name, score)a 10
b 20
c 30
d 40
for a, b in zip(["a", "b", "c"], [1, 2]):
print(a, b)a 1
b 2
When the lists are not of the same size, zip stops when the smallest one ends.
Iterating over the index and the element together
dates = [
"Sep 21",
"Sep 25",
"Oct 05",
"Oct 09"]for i in range(len(dates)):
print(f"Session {i}: {dates[i]}")Session 0: Sep 21
Session 1: Sep 25
Session 2: Oct 05
Session 3: Oct 09
This is not very Pythonic!
for i, date in enumerate(dates):
print(f"Session {i}: {date}")Session 0: Sep 21
Session 1: Sep 25
Session 2: Oct 05
Session 3: Oct 09
for i, date in enumerate(dates):
print(f"Session {i+1}: {date}")Session 1: Sep 21
Session 2: Sep 25
Session 3: Oct 05
Session 4: Oct 09
for i, date in enumerate(dates, start=1):
print(f"Session {i}: {date}")Session 1: Sep 21
Session 2: Sep 25
Session 3: Oct 05
Session 4: Oct 09
What if we want only dates for the even sessions?
[date for i, date in enumerate(dates, start=1) if i%2 == 0]['Sep 25', 'Oct 09']
Example: Count Increased
%load_problem count-increasedGiven a list of numbers, count the number of times a number in the list increases from the previous number.
For example:
Given the list of numbers as [3, 5, 4, 9, 2, 8]
3 (N/A - no previous number)
5 (increased)
4 (decreased)
9 (increased)
2 (decreased)
8 (increased)
So the number of times a number increased from the previous number is 3.
Write a function count_increased that takes a list of numbers as arguments and returns the number of times a number in that list is increased from the previous one.
>>> count_increased([3, 5, 4, 9, 2, 8])
3
Credits: this problem is modelled after Day 1 problem of Advent of Code 2021.
You can verify your solution using:
%verify_problem count-increased
numbers = [3, 5, 4, 9, 2, 8]3 5 4 9 2 8
- 3 4 5 9 2
0 1 0 1 0 1
[a>b for a, b in zip(numbers[1:], numbers)][True, False, True, False, True]
list(zip(numbers[1:], numbers))[(5, 3), (4, 5), (9, 4), (2, 9), (8, 2)]
sum([a>b for a, b in zip(numbers[1:], numbers)])3