L03 - Python (Part 2) - 1
L03 - Python (Part 2) - 1
Python (part 2)
Data Visualization (C1VI1B) Autumn 2025
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 1 (65) Python (part 2) UNIVERSITY OF BORÅS
Agenda
• Iteration and Search
• Strings
• Lists
• Dictionaries
• Tuples
• Sets
• List and Dictionary Comprehensions
• Files
• Object-Oriented Programming
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 2 (65) Python (part 2) UNIVERSITY OF BORÅS
Iteration and Search
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 3 (65) Python (part 2) UNIVERSITY OF BORÅS
The for Loop
• A for statement (loop) iterates (loops) head for variable in iterable:
over a block of statements a number of body statements
times. Indented colon
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 4 (65) Python (part 2) UNIVERSITY OF BORÅS
Loops and Strings
• The iterable can be sequence of integers from the range function. for i in range(3): main.py
0 1 2
• The iterable can also be a sequence of characters in a string. for letter in 'Gadsby': main.py
G a d s b y
print( has_e('Gadsby') )
print( has_e('Emma') )
False
True
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 5 (65) Python (part 2) UNIVERSITY OF BORÅS
The in Operator
• We have seen the in operator used in a for loop to extract the def has_e(word): main.py
False
word = 'Gadsby' True
'e' in word # False
print( has_e('Gadsby') )
print( has_e('Emma') )
False
True
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 6 (65) Python (part 2) UNIVERSITY OF BORÅS
Strings
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 7 (65) Python (part 2) UNIVERSITY OF BORÅS
A String is a Sequence
• A string is a sequence of characters. fruit = 'banana' main.py
letter = fruit[0] # letter contains the character 'b' letter = fruit[ len(fruit)-1 ]
print(letter)
• We can use the built-in function len to get the length of a sequence (number of elements).
letter = fruit[-1]
len('banana') # the length of 'banana' is 6 print(letter)
letter = fruit[-2]
• The last index of a sequence is len(sequence) – 1. print(letter)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 8 (65) Python (part 2) UNIVERSITY OF BORÅS
String Slices
• A subset of elements in a sequence is called a slice. fruit = 'banana' main.py
• The operator [n:m] returns the slice from index n to index m-1. slice = fruit[0:3]
print(slice)
fruit = 'banana'
slice = fruit[3:6]
slice = fruit[0:3] # slice contains 'ban' print(slice)
slice = fruit[3:6] # slice contains 'ana'
slice = fruit[:3]
• If you omit the first index, the slice starts at the beginning of the sequence. print(slice)
slice = fruit[3:]
fruit = 'banana' print(slice)
slice = fruit[:3] # slice contains 'ban'
slice = fruit[2:-1]
• If you omit the second index, the slice goes to the end of the sequence. print(slice)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 9 (65) Python (part 2) UNIVERSITY OF BORÅS
Strings are Immutable
• Strings are immutable, i.e. you can’t change an existing greeting = 'Hello, world!' main.py
• Trying to change an element, e.g. the first character, > python main.py terminal
TypeError: 'str' object does not support item assignment
results in a TypeError.
• Although, you can create a new string based on parts of greeting = 'Hello, world!' main.py
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 10 (65) Python (part 2) UNIVERSITY OF BORÅS
String Comparison
• The relational operators work on strings, e.g. to check if word = 'banana' main.py
• We can use other relational operations e.g. > and <. def compare_word(word): main.py
if word < 'banana':
print(word, 'comes before banana.')
• Python compares strings (and characters) based on their elif word > 'banana':
integer character codes from a unicode character set. print(word, 'comes after banana.')
else:
https://en.wikipedia.org/wiki/List_of_Unicode_characters print('All right, banana.')
word = 'banana'
• Therefore the character 'P' (80) comes before 'b' (98). compare_word('apple')
compare_word('Pineapple')
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 11 (65) Python (part 2) UNIVERSITY OF BORÅS
String Methods
• Strings provide methods that perform a variety of useful operations. word = 'BaNanA' main.py
terminal
> python main.py
'BANANA'
'banana'
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 12 (65) Python (part 2) UNIVERSITY OF BORÅS
Lists
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 13 (65) Python (part 2) UNIVERSITY OF BORÅS
A List is a Sequence
• Like a string, a list is a sequence of values. lst = [] main.py
print(lst)
• In a string, the elements are characters, but in a list, the elements can be any type.
lst = [42, 123]
• The simplest way to create a list is to enclose the elements in square brackets [] print(lst)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 14 (65) Python (part 2) UNIVERSITY OF BORÅS
Lists are Mutable
• To read an element of a list, we can use the bracket operator [] with cheeses = ['Cheddar', 'Edam', 'Gouda'] main.py
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 15 (65) Python (part 2) UNIVERSITY OF BORÅS
List Slices
• The slice operator [n:m] works the same way on lists and strings. main.py
copy = list(letters)
slice = letters[2:] # slice contains ['c', 'd'] print(copy)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 16 (65) Python (part 2) UNIVERSITY OF BORÅS
List Operations
• The + operator concatenates lists. t1 = [1, 2] main.py
t2 = [3, 4]
print(t1 + t2)
t1 = [1, 2]
t2 = [3, 4] spam = ['spam'] * 4
lst = t1 + t2 # lst contains [1, 2, 3, 4] print(spam)
total = sum(t1)
• The * operator repeats a list a given number of times. print(total)
• The built-in functions min and max find the smallest and largest elements. [1, 2, 3, 4]
['spam', 'spam', 'spam', 'spam']
3
smallest = min([1, 2]) # smallest is 1 1
largest = max([3, 4]) # largest is 4 4
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 17 (65) Python (part 2) UNIVERSITY OF BORÅS
List Methods A method is like a function, but it is bound to an object (instance of a string, list,
etc.) and is accessed via the dot operator . using the syntax object.method()
• Python provides methods that operate on lists, such as the append method that adds letters = ['a', 'b', 'c', 'd'] main.py
a new element to the end of a list.
letters.append('e')
letters = ['a', 'b', 'c', 'd'] print(letters)
letters.append('e') # ['a','b','c','d','e']
letters.extend(['f', 'g'])
• The extend method takes a list as an argument and appends all of the elements.
print(letters)
letters = ['a', 'b', 'c', 'd']
t = ['a', 'b', 'c']
letters.extend(['f', 'g']) # ['a','b','c','d','e','f','g']
e = t.pop(1)
print(e)
• The pop method takes an index as an argument and removes the element from a list.
print(t)
t = ['a', 'b', 'c']
e = t.pop(1) # e contains 'b' and t contains ['a', 'c'] t = ['a', 'b', 'c']
t.remove('b')
print(t)
• The remove method takes an element as an argument and removes the element from
a list (if the element is not in the list, that’s a ValueError). t.remove('d')
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 18 (65) Python (part 2) UNIVERSITY OF BORÅS
Lists and Strings
• A string is a sequence of characters and a list is a sequence of values. s = 'spam' main.py
t = list(s)
• A list of characters is not the same as a string. print(t)
• To convert from a string to a list of characters, you can use the list function.
s = 'pining for the fjords'
t = s.split()
s = 'spam' print(t)
t = list(s) # ['s', 'p', 'a', 'm']
s = 'ex-parrot'
• The split method breaks a string into a list of words (strings). t = s.split('-')
print(t)
s = 'pining for the fjords'
t = s.split() # ['pining', 'for', 'the', 'fjords'] delimiter = ' '
t = ['pining', 'for', 'the', 'fjords']
• An optional argument called a delimiter specifies which characters to use as word s = delimiter.join(t)
boundaries. print(s)
s = 'ex-parrot'
t = s.split('-') # ['ex', 'parrot'] > python main.py terminal
['s', 'p', 'a', 'm']
• You can concatenate a list of strings into a single string using the join method. ['pining', 'for', 'the', 'fjords']
['ex', 'parrot']
delimiter = ' ' 'pining for the fjords'
t = ['pining', 'for', 'the', 'fjords']
delimiter.join(t) # 'pining for the fjords'
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 19 (65) Python (part 2) UNIVERSITY OF BORÅS
Looping Through and Sorting a List
• You can use a for statement to loop through the elements of a list. cheeses = ['Cheddar', 'Edam', 'Gouda'] main.py
for cheese in cheeses:
cheeses = ['Cheddar', 'Edam', 'Gouda'] print(cheese)
for cheese in cheeses:
scramble = ['c', 'a', 'b']
print(cheese) scramble = sorted(scramble)
print(scramble)
• The built-in function called sorted sorts the elements of a list.
lst = sorted('letters')
scramble = ['c', 'a', 'b'] print(lst)
sorted(scramble) # ['a', 'b', 'c']
> python main.py terminal
• sorted works with any kind of sequence. Cheddar
Edam
sorted('letters') # ['e','e','l','r','s','t','t'] Gouda
['a', 'b', 'c']
['e', 'e', 'l', 'r', 's', 't', 't']
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 20 (65) Python (part 2) UNIVERSITY OF BORÅS
Objects, References and Values (Strings)
• Assigning a string to a variable, means we are creating an object (instance) of a string with a value, where the
variable has a reference (arrow) to the object.
• If we assign a string with the same value to another variable, there are 2 possible outcomes:
• they are referring to two different objects (left)
• they are referring to the same object (right)
a = 'banana'
b = 'banana'
• Python only created one string object (right), and both variables refer to the same object. terminal
> python main.py
True
a == b # True
True
a is b # True
• The reason for this is that strings are immutable, and Python has optimized memory by creating one object.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 21 (65) Python (part 2) UNIVERSITY OF BORÅS
Objects, References and Values (lists)
• Assigning a list to a variable, means we are creating an object (instance) of a list with a value, where the variable
has a reference (arrow) to the object.
• If we assign a list with the same value to another variable, there are 2 possible outcomes:
• they are referring to two different objects (left)
• they are referring to the same object (right)
a = [1, 2, 3]
b = [1, 2, 3]
• Python created two list objects (left), where each variable is referring to different objects. terminal
> python main.py
True
a == b # True
False
a is b # False
• The reason for this is that list are mutable, so Python needs to create two objects.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 22 (65) Python (part 2) UNIVERSITY OF BORÅS
Aliasing
• If variable a refers to an object and you assign it to another variable b = a, a = [1, 2, 3] main.py
then both variables refer to the same object. b = a
a = [1, 2, 3]
print( b == a )
b = a
print( b is a )
b == a # True
b is a # True
b[0] = 5
• An object with more than one reference has more than one name (a and b), print(b)
so we say the object is aliased. print(a)
• If the aliased object is mutable, changes made with one name affect the other
> python main.py terminal
(if we change the object b refers to, we are also changing the object a refers to).
True
True
b[0] = 5 # now both b and a contain [5, 2, 3] (same object) [5, 2, 3]
• This behavior can be useful, but it’s error-prone. Avoid aliasing when working with mutable objects.
a = 'banana'
b = 'banana'
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 23 (65) Python (part 2) UNIVERSITY OF BORÅS
Passing Lists as Arguments to Functions
• When you pass a list to a function, the function gets a reference to the list. def pop_first(lst): main.py
return lst.pop(0)
letter = pop_first(letters)
letters = ['a', 'b', 'c'] print(letter)
letter = pop_first(letters) print(letters)
• The parameter lst and the variable letters are aliases for the same object.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 24 (65) Python (part 2) UNIVERSITY OF BORÅS
Dictionaries
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 25 (65) Python (part 2) UNIVERSITY OF BORÅS
A Dictionary is a Mapping
• A dictionary is like a list, but more general. In a dictionary, the indexes (called keys) can be (almost) any type. numbers = {} main.py
•
print(numbers)
To create an empty dictionary, we use curly braces {}
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 26 (65) Python (part 2) UNIVERSITY OF BORÅS
Creating Dictionaries
• An empty dictionary can be created using curly braces {}. numbers = {} main.py
print(numbers)
numbers = {}
numbers['zero'] = 0
numbers['one'] = 1
• Then items can be added using the square bracket operator []. numbers['two'] = 2
print(numbers)
numbers['zero'] = 0
numbers = {'zero': 0, 'one': 1, 'two': 2}
numbers['one'] = 1 print(numbers)
numbers['two'] = 2
numbers_copy = dict(numbers)
print(numbers_copy)
• A dictionary can be initialized with items within {} during creation.
numbers = dict()
numbers = {'zero': 0, 'one': 1, 'two': 2} print(numbers)
numbers = dict()
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 27 (65) Python (part 2) UNIVERSITY OF BORÅS
The in Operator
• A dictionary’s items are stored in a hash table using the key numbers = {'zero': 0, 'one': 1, 'two': 2} main.py
as a hash value. print(numbers)
• The in operator can be used to check if a key exists in a key_exists = 'one' in numbers
print(key_exists)
dictionary.
key_exists = 'three' in numbers
numbers = {'zero': 0, 'one': 1, 'two': 2} print(key_exists)
key_exits = 'one' in numbers # True
value_exists = 1 in numbers.values()
key_exits = 'three' in numbers # False print(value_exists)
value_exists = 3 in numbers.values()
• The in operator can be used with the values method to print(value_exists)
check if a value exists in a dictionary.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 28 (65) Python (part 2) UNIVERSITY OF BORÅS
Looping and Dictionaries
• Using a for loop on a dictionary traverses the keys. main.py > python main.py terminal
{'b': 1, 'a': 3, 'n': 2}
counter = {'b': 1, 'a': 3, 'n': 2} counter = {'b':1, 'a':3, 'n':2} b
print(counter) a
for key in counter: n
print(key) for key in counter: 1
print(key) 3
• Using the values method, we can traverse the values. 2
for value in counter.values(): b 1
print(value) a 3
for value in counter.values(): n 2
print(value) for key in counter: b 1
value = counter[key] a 3
• We can traverse the keys and look up the corresponding print(key, value) n 2
values.
for key, value in counter.items():
for key in counter: print(key, value)
value = counter[key]
print(key, value)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 29 (65) Python (part 2) UNIVERSITY OF BORÅS
Lists and Dictionaries
• You can put a list in a dictionary as a value. main.py
key = 4
key = 4 value = ['r', 'o', 'u', 's']
value = ['r', 'o', 'u', 's'] d = {key: value}
d = {key: value} print(d)
• A key has to be immutable to be hashable (its hash value is always the same).
• Since lists and dictionaries are mutable, they can’t be used as keys.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 30 (65) Python (part 2) UNIVERSITY OF BORÅS
Tuples
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 31 (65) Python (part 2) UNIVERSITY OF BORÅS
Tuples are Like Lists
t = 'l', 'u', 'p', 'i', 'n' main.py
• Just like a list, a tuple is a sequence of values of any type, indexed by integers. print(t)
print( type(t) )
• Whereas lists are mutable, tuples are immutable.
t = ('l', 'u', 'p', 'i', 'n')
• To create a tuple, you can use a comma-separated list of values, optionally print(t)
within parentheses (). print( type(t) )
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 32 (65) Python (part 2) UNIVERSITY OF BORÅS
Most List Operators Work with Tuples
• The bracket operator [] indexes an element. t = ('l', 'u', 'p', 'i', 'n') main.py
print(t)
t = ('l', 'u', 'p', 'i', 'n')
element = t[0] # element contains 'l' element = t[0]
print(element)
• The slice operator [n:m] selects a range of elements.
slice = t[1:3]
print(slice)
slice = t[1:3] # slice contains ('u', 'p')
t = tuple('lup') + ('i', 'n')
• The + operator concatenates tuples. print(t)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 33 (65) Python (part 2) UNIVERSITY OF BORÅS
But Tuples are Immutable
• Tuples are immutable, so if you try to modify a tuple, you get a t = ('l', 'u', 'p', 'i', 'n') main.py
TypeError. t[0] = 'L'
t[0] = 'L' # TypeError TypeError: 'tuple' object does not support item assignment
• Since tuples are immutable, they are hashable, and can be used t = ('lupin') main.py > python main.py terminal
as keys in a dictionary. print(t) ('l', 'u', 'p', 'i', 'n')
{(1, 2): 3, (3, 4): 7}
d = {} # {} d = {} 3
d[(1, 2)] = 3 7
d[(1, 2)] = 3 # {(1, 2): 3}
d[(3, 4)] = 7 {'key': ('a', 'b', 'c')}
d[(3, 4)] = 7 # {(1, 2): 3, (3, 4): 7} print(d)
• We can look up a value in a dictionary using tuples as keys. value = d[(1, 2)]
print(value)
value = tuple('abc')
• Tuples can also appear as values in a dictionary. d = {'key': value}
print(d)
value = tuple('abc')
d = {'key': value} # {'key': ('a', 'b', 'c')}
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 34 (65) Python (part 2) UNIVERSITY OF BORÅS
Tuple Assignment
a, b = 1, 2 main.py
• You can assign a tuple of values to a tuple of variables. print(a, b)
a, b = 1, 2, 3
• The number of variables and number of values must match otherwise you get a
ValueError. terminal
> python main.py
(1, 2)
a, b = 1, 2, 3 # ValueError
(1, 2)
monty python.org
• Since all expressions on the right side are evaluated before any assignments, we can ValueError: too many values to unpack (expected 2)
swap variable values using tuple assignment.
a, b = 1, 2 main.py terminal
a, b = 1, 2
a, b = b, a # now a contains 2, b contains 1 a, b = b, a > python main.py
print(a, b) (2, 1)
one, 1
• To loop through the items in a dictionary, we can use the items method, where a key d = {'one': 1, 'two': 2} two, 2
and its corresponding value are assigned to the loop variables using tuple assignment. one, 1
for item in d.items(): two, 2
d = {'one': 1, 'two': 2} key, value = item
print(key, value)
for key, value in d.items(): # key, value = item
print(key, value) for key, value in d.items():
print(key, value)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 35 (65) Python (part 2) UNIVERSITY OF BORÅS
Tuples as Return Values and Argument Packing
• A function can only return one value, but if the value is a tuple, the effect is the same as def min_max(lst): main.py
returning multiple values. return min(lst), max(lst)
• A function parameter that begins with the * operator packs multiple arguments into a tuple. def mean(*args):
return sum(args) / len(args)
def mean(*args): # values 1, 2, 3 packed into tuple (1,2,3)
return sum(args) / len(args) average = mean(1, 2, 3)
print(average)
average = mean(1, 2, 3) # average contains 2.0
def add(a, b):
• To pass a sequence of values to a function as multiple arguments, you can use the * operator return a + b
to unpack the tuple.
lst = [1, 2]
def add(a, b): sum = add(*lst)
return a + b print(sum)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 36 (65) Python (part 2) UNIVERSITY OF BORÅS
The zip and enumerate Functions
• The zip function takes two or more sequences and returns a zip object, that groups the main.py
scores1 = [1, 2, 4, 5]
elements with matching indexes in the sequences. scores2 = [5, 5, 2, 2]
# 1 5
# 2 5 > python main.py terminal
# 4 2 <zip at 0x7f23a9a7bdc0>
# 5 2 1, 5
2, 5
4, 2
• The function enumerate includes indexes when looping over a sequence. 5, 2
0, 1
for index, score1 in enumerate(scores1): 1, 2
print(index, score1) 2, 4
3, 5
# 0 1
# 1 2
# 2 4
# 3 5
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 37 (65) Python (part 2) UNIVERSITY OF BORÅS
Sets
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 38 (65) Python (part 2) UNIVERSITY OF BORÅS
Sets are Unordered Collections
• Sets are unordered collections of unique values. main.py
s = {1, 2, 2, 3}
• To create a set, you can use a comma-separated list of values within curly print(s)
braces {}. print( type(s) )
s = set()
s = {1, 2, 2, 3} # s is a set print(s)
print( type(s) )
• Sets are unordered collections of unique values (notice only one 2).
d = {}
print(s) # {2, 3, 1} print(d)
print( type(d) )
• You can create an empty set with built-in function set.
> python main.py terminal
s = set() # s is an empty set {2, 3, 1}
<class: 'set'>
d = {} # you can’t use {} which creates an empty dict
set()
<class: 'set'>
{}
<class: 'dict'>
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 39 (65) Python (part 2) UNIVERSITY OF BORÅS
Sets are Mutable
• Sets are mutable. main.py
s = {1, 2}
print(s)
s = {1, 2} # {1, 2}
• To add a single element. s.add(3) # Add single element
print(s)
s.add(3) # {1, 2, 3} s.update([4, 5]) # Add multiple elements
print(s)
• To add multiple elements.
s.remove(2) # Removes 2, raises KeyError if not found
s.update([4, 5]) # {1, 2, 3, 4, 5} print(s)
• To remove an element (raises KeyError if not found). s.discard(10) # Safe remove — does nothing if not found
print(s)
s.remove(2) # {1, 3, 4, 5} s.clear() # Empties the set > python main.py terminal
print(s) {1, 2}
• To remove an element (does nothing if not found). {1, 2, 3}
{1, 3, 4, 5}
• To empty a set.
{1, 3, 4, 5}
s.clear() # set()
set()
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 40 (65) Python (part 2) UNIVERSITY OF BORÅS
Sets Support Set Operations
• We can perform set operations on sets. terminal
a = {1, 2, 3} main.py > python main.py
b = {3, 4, 5} {1, 2, 3}
a = {1, 2, 3} # {1, 2, 3} {3, 4, 5}
print(a)
b = {3, 4, 5} # {3, 4, 5}
print(b)
{1, 2, 3, 4, 5}
• Union.
print(a | b) # union {3}
a | b # {1, 2, 3, 4, 5}
{1, 2}
print(a & b) # intersection
• Intersection.
{1, 2, 4, 5}
a & b # {3} print(a - b) # difference
True
• Symmetric Difference.
a ^ b # {1, 2, 4, 5}
• Membership testing.
3 in a # True
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 41 (65) Python (part 2) UNIVERSITY OF BORÅS
Frozensets are Immutable Sets
• Frozensets are immutable versions of sets. main.py
fs = frozenset({1, 2, 3})
• To create a frozenset, you use the built-in function frozenset. print(fs)
print(type(fs))
fs = frozenset({1, 2, 3}) # fs is a frozenset
fs.add(4) # Error: frozensets are immutable
• Frozensets are immutable.
> python main.py terminal
fs.add(4) # Error frozenset({1, 2, 3})
<class: 'frozenset'>
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 42 (65) Python (part 2) UNIVERSITY OF BORÅS
List and Dictionary Comprehensions
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 43 (65) Python (part 2) UNIVERSITY OF BORÅS
List Comprehensions
• A list comprehension creates a list using a for loop inside the square element for loop condition
brackets [] to generate the list’s elements.
lst = [variable for variable in iterable]
words = ['level\n', 'car\n', 'madam\n', 'dog\n']
word_list = [word.strip() for word in words] lst = [variable for variable in iterable if condition]
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 44 (65) Python (part 2) UNIVERSITY OF BORÅS
Dictionary Comprehensions
• A dictionary comprehension creates a dictionary using a for loop item for loop condition
inside the curly braces {} to generate the dictionary’s items.
d = {item for item in iterable}
words = ['level', 'car', 'madam', 'dog']
word_dict = {key:value d = {item for item in iterable if condition}
for key, value in enumerate(words)}
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 45 (65) Python (part 2) UNIVERSITY OF BORÅS
Files
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 46 (65) Python (part 2) UNIVERSITY OF BORÅS
Filenames and Paths
import os main.py
• The os module provides functions for working with files and directories.
• The os.getcwd function returns the path to the current working directory. path = os.path.abspath('memo.txt')
print(path)
cwd = os.getcwd()
lst = os.listdir(cwd)
• The os.path.abspath function takes a path to a file or directory and returns its absolute path. print(lst)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 47 (65) Python (part 2) UNIVERSITY OF BORÅS
Reading and Writing Files Mode Meaning
'r' Read (default, file must exist)
'w' Write (creates file or truncates it)
• The open function takes a filepath and a mode, opens a file, returning a file handle. 'a’ Append (creates if doesn’t exist
f = open('example.txt', 'w') ‘b’ Binary mode (add to any of the above, e.g. 'rb')
‘+’ Read and Write (add to any of the above, e.g. 'rb+')
• The file modes are 'r' for reading, 'w' for writing, and 'a' for appending from/to a text file.
with open('example.txt', 'w') as f: main.py
• Adding a 'b', ie. 'rb', 'wb', 'ab', processes the file as a binary file. f.write("Hello\n")
• Adding a '+', ie. 'r+', 'w+', 'a+’, 'rb+’, 'wb+', 'ab+', enables reading and writing/appending. f.write("World\n")
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 48 (65) Python (part 2) UNIVERSITY OF BORÅS
f-strings
• An f-string is a string preceded by the letter f.
• An f-string accepts curly braces {} as a placeholder for an expression (value, variable, etc.) inside the string, where the placeholder is replaced with the
value of the expression.
num_camels = 23
s = f'I have spotted {num_camels} camels' # 'I have spotted 23 camels'
num_years = 1.5
num_camels = 23
s = f'In {num_years} years I have spotted {num_camels} camels' # 'I have spotted 23 camels'
num_years = 1.5 main.py
num_camels = 23
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 49 (65) Python (part 2) UNIVERSITY OF BORÅS
f-strings
• The expressions can contain operators and function calls.
num_years = 1.5
num_camels = 23
line = f'In {round(num_years * 12)} months I have spotted {num_camels} camels'
t = [1, 2, 3]
d = {'one': 1}
s = f'Here is a list {t} and a dictionary {d}'
t = [1, 2, 3]
d = {'one': 1}
s = f'Here is a list {t} and a dictionary {d}'
print(s)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 50 (65) Python (part 2) UNIVERSITY OF BORÅS
Object-Oriented Programming
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 51 (65) Python (part 2) UNIVERSITY OF BORÅS
Programmer-Defined Types (Classes)
• Object-oriented programming uses programmer-defined types to organize code and data. class name colon
A programmer-defined type is also called a class.
head class name:
• A class definition has:
body # definition goes here
• A head, consisting of the keyword class, followed by its name and a colon :
• A body, consisting of the definition of the class indented under the head. Indented
• Usually, the first part of the body contains a docstring, describing the class.
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 52 (65) Python (part 2) UNIVERSITY OF BORÅS
Attributes and Methods
• A class can contain functions called methods. A class contains a special method __init__() called a constructor. main.py
class Card: class Card:
"""Represents a standard plying card.""" """Represents a standard playing card."""
def __init__(self): def __init__(self, suit, rank):
# initialization code goes here
self.suit = suit
self.rank = rank
• When a new instance (object) of a class is created, the constructor is called, where the first parameter called self is
implicitly set to the new instance (object) of the class that is being created.
def to_tuple(self):
card = Card() # create an object of type Card, assign it to variable card return (self.suit, self.rank)
• A class can contain variables, called attributes, which are defined and initialized in the constructor. card = Card('Spades', 'Ace')
def __init__(self, suit, rank): # constructor now has parameters
self.suit = suit # initialize attribute self.suit with value suit print(type(card))
self.rank = rank # initialize attribute self.rank with value rank print(card)
print(card.suit, card.rank)
• Since the constructor now takes 2 arguments (in addition to self), we need to supply them when creating an object. print(card.to_tuple())
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 53 (65) Python (part 2) UNIVERSITY OF BORÅS
Attributes and Methods class Card:
main.py
• Notice that we have direct access to the attributes from the instance (object). def __init__(self, suit, rank):
self.__suit = suit
card = Card('Spades', 'Ace') self.__rank = rank
card.suit = 'Hearts'
card.rank = '9'
@property
print(card.suit, card.rank) # prints out Hearts 9
def suit(self):
return self.__suit
• This means the attributes have public visibility, i.e. can be accessed directly (read from, and written to) from an
instance (object) via the dot operator. This isn’t ideal, since any client code could change the attribute’s values
directly, with any values. Instead we want to access an attribute via a getter method (returns its value) and a # @suit.setter
setter method (sets its value). The client code is unchanged, but access is then via the getter and setter. # def suit(self, value):
# self.__suit = value
• First we make sure the attributes have private visibility, i.e. are only accessible from code within the class
definition, by preceding an attribute name with 2 underscores __ @property
def rank(self):
def __init__(self, suit, rank): return self.__rank
self.__suit = suit # self.__suit
self.__rank = rank # self.__rank # @rank.setter
# def rank(self, value):
• Then we define a getter and setter method with the syntax below. Notice the decorators @property and # self.__rank = value
@attributename.setter, where attributename is the name of the property. Also notice the getter
and setter methods have the same name as the attribute. The getter takes no arguments and returns the
attribute’s value. The setter takes one argument (value) and sets the attribute’s value. def to_tuple(self):
return (self.suit, self.rank)
@property Omit the setter to prevent
def suit(self): card = Card('Spades', 'Ace')
writing to the attribute
return self.__suit # self.__suit print(card.suit, card.rank)
card.suit = 'Hearts'
@suit.setter
def suit(self, value): > python main.py terminal
self.__suit = value # self.__suit = value Spades Ace
AttributeError: property 'suit' of 'Card' object has no setter
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 54 (65) Python (part 2) UNIVERSITY OF BORÅS
Class Attributes
class Card: main.py
• The attributes we have just defined are called instance attributes, since they are only accessible from an instance (object) via dot rank_names = [None, 'Ace', '2', '3',
notation card.suit '4', '5', '6', '7',
'8', '9', '10', 'Jack',
• A class attribute, is accessible via the class using dot notation. Class attributes are defined outside any method and without the 'Queen', 'King', 'Ace']
preceding self, such as the attributes suit_names and rank_names below.
def __init__(self, suit, rank):
class Card: self.__suit = suit
self.__rank = rank
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None,'Ace','2','3','4','5','6','7','8', @property
'9', '10','Jack','Queen','King','Ace'] def suit(self):
return self.__suit
def __init__(self, suit, rank):
self.__suit = suit
self.__rank = rank @property
def rank(self):
return self.__rank
• Class attributes belong to the class itself, and not to any instance (object), so changing the value of a class attribute via the class
name, changes the value for all objects. def to_tuple(self):
return (self.suit, self.rank)
card1 = Card('Spades', 'Ace')
card2 = Card('Hearts', '9')
print(Card.suit_names) # ['Clubs','Diamonds','Hearts','Spades'] card1 = Card('Spades', 'Ace')
card2 = Card('Hearts', '9')
Card.suit_names = ['One', 'Two', 'Three', 'Four']
print(card1.suit_names) # ['One', 'Two', 'Three', 'Four'] print(card1.suit_names)
print(card2.suit_names) # ['One', 'Two', 'Three', 'Four'] print(card2.suit_names)
print(Card.suit_names) # ['One', 'Two', 'Three', 'Four'] print(Card.suit_names)
• In Python you can actually access and change a class attribute via an instance (object), but that just creates an instance attribute that Card.suit_names = ['One', 'Two', 'Three', 'Four']
overrides the class attribute for that instance (object), print(card1.suit_names) > python main.py
terminal
print(card2.suit_names)
card1.suit_names = ['Five', 'Six', 'Seven', 'Eight'] print(Card.suit_names) ['Clubs','Diamonds','Hearts','Spades']
print(card1.suit_names) # ['Five', 'Six', 'Seven', 'Eight'] ['Clubs','Diamonds','Hearts','Spades']
print(card2.suit_names) # ['One', 'Two', 'Three', 'Four'] ['Clubs','Diamonds','Hearts','Spades']
print(Card.suit_names) # ['One', 'Two', 'Three', 'Four'] ['One','Two','Three','Four']
['One','Two','Three','Four']
['One','Two','Three','Four']
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 55 (65) Python (part 2) UNIVERSITY OF BORÅS
Special Methods class Card: main.py
suit_names = ['Clubs','Diamonds','Hearts','Spades']
• Special methods’ names start and end with __ rank_names = [None,'Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King','Ace']
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 56 (65) Python (part 2) UNIVERSITY OF BORÅS
Modules and Packages
• Currently, when we print out the type of a Card object we get.
├── main.py
card = Card(0, 2)
print(type(card)) # <class '__main__.Card'> └── poker
├── card.py
• The new object is of type __main__.Card, where __main__ is the name of the module in which Card class is
defined. └── __init__.py
• A module is simply a .py file, e.g. main.py, which can contain class definitions, functions, variables, etc.
• The __main__ module is actually a special module. The Python file that we supply as an argument to the Python
interpreter, e.g. python main.py is designated as the __main__ module, irrespective of the name of the
Python file, and is where we usually have our main program code.
• We can group modules (.py files) into packages, where a package contains a collection of related modules. For
example, a math package might contain various modules (.py files) for performing mathematical calculations such
as sqrt, sin, cos, etc.
• A package is just a folder that contains a set of modules (.py files), including a special file called __init__.py.
This file can be empty, but is actually what makes a folder a package, where the name of the package is the folder class Card: card.py
name.
# code for the Card class here
• Let’s move our class definition code to a module called card.py, and place it in a folder called poker. Finally, let’s
add an empty file __init__.py in the poker folder, hence creating a package called poker, with one module in
it, i.e. the file card.py. main.py
from poker.card import Card
• In main.py, let’s use the code below, which imports the Card class from the card module in the poker package,
creates an instance (object) of the Card class, and prints out its type. card = Card(0, 2)
print(type(card))
from poker.card import Card
card = Card(0, 2)
print(type(card)) # <class 'poker.card.Card'>
> python main.py terminal
• The print out of the instance (object) now tells us the Card class is in the card module in the poker package. <class 'poker.card.Card'>
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 57 (65) Python (part 2) UNIVERSITY OF BORÅS
Modules and Packages
• There are multiple ways to import packages, modules, classes, functions and variables.
├── main.py
# import the whole poker package
# to access the Card class, we need to use card1 = poker.card.Card(0, 2) └── poker
import poker ├── card.py
# import the whole poker package, under the alias p └── __init__.py
# to access the Card class, we need to use card1 = p.card.Card(0, 2)
import poker as p
# import the whole card module, under the alias card, from the poker package
# to access the Card class, we need to use card1 = card.Card(0, 2)
import poker.card as card
# import the Card class, from the card module, in the poker package
# to access the Card class, we need to use card1 = Card(0, 2)
from poker.card import Card
# import the Card class, under the alias C, from the card module, in the poker package
# to access the Card class, we need to use card1 = C(0, 2)
from poker.card import Card as C card.py
class Card:
# code for the Card class here
• In this case, it makes sense to use the second last alternative, so we can use the Card class directly.
from poker.card import Card main.py
from poker.card import Card
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 58 (65) Python (part 2) UNIVERSITY OF BORÅS
The Card Class in Action
from poker.card import Card > python main.py terminal
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 59 (65) Python (part 2) UNIVERSITY OF BORÅS
import random deck.py
class Deck:
def __init__(self, label=''):
self._cards = self._make_cards()
• Let’s add another module deck.py to the poker package.
• Create a deck.py file (module) in the poker folder (package)
├── main.py @property
def cards(self):
• import the Card class from the card.py module ├── card.py def _make_cards(self):
cards = []
• Notice the relative path .card, i.e. poker/card.py ├── deck.py for suit in range(4):
for rank in range(2, 15):
• It’s common practice to use relative paths within the same package └── __init__.py card = Card(suit, rank)
cards.append(card)
• Define a Deck class in deck.py return cards
• The constructor calls the protected method self._make_cards, which returns a list of 52 def move_cards(self, other, num):
cards, and assigns the list to the protected attribute self._cards for i in range(num):
card = self.take_card()
• A protected attribute is like a private attribute with respect to client programs, other.put_card(card)
but like a public attribute for inheriting subclasses (we’ll see this shortly).
def take_card(self):
• Likewise, a protected method is not visible for client programs, but visible for inheriting return self._cards.pop()
subclasses.
def put_card(self, card):
• The take_card method removes and returns a card from the deck, and the put_card self._cards.append(card)
method takes a card as an argument and adds it to the deck. def shuffle(self):
• The move_cards method takes another Deck and a number as arguments, and moves the random.shuffle(self._cards)
first number of cards from the current Deck to the other Deck. It calls the take_card and def sort(self):
put_card methods to move a card. self._cards.sort()
• The shuffle method calls the shuffle method from the random package to shuffle the def __str__(self):
deck of cards, and the sort method sorts the deck of cards. res = []
for card in self._cards:
• The special method __str__ returns a string representation of a Deck object. res.append(str(card))
return '\n'.join(res)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 60 (65) Python (part 2) UNIVERSITY OF BORÅS
import random deck.py
class Deck:
def __init__(self):
self._cards = self._make_cards()
from poker.card import Card main.py > python main.py terminal @property
from poker.deck import Deck def cards(self):
return self._cards
deck = Deck()
print(len(deck.cards)) 52 def _make_cards(self):
cards = []
card = deck.take_card() for suit in range(4):
print(card) Ace of Spades for rank in range(2, 15):
print(len(deck.cards)) 51 card = Card(suit, rank)
cards.append(card)
deck.put_card(card) return cards
print(len(deck.cards)) 52
def move_cards(self, other, num):
print() 2 of Clubs for i in range(num):
for card in deck.cards[:4]: 3 of Clubs card = self.take_card()
print(card) 4 of Clubs other.put_card(card)
print() 5 of Clubs
def take_card(self):
deck.shuffle() 5 of Hearts return self._cards.pop()
for card in deck.cards[:4]: 7 of Spades
print(card) 10 of Spades def put_card(self, card):
print() 2 of Diamonds self._cards.append(card)
deck.sort() 2 of Clubs def shuffle(self):
for card in deck.cards[:4]: 3 of Clubs random.shuffle(self._cards)
print(card) 4 of Clubs
5 of Clubs def sort(self):
self._cards.sort()
def __str__(self):
res = []
for card in self._cards:
res.append(str(card))
return '\n'.join(res)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 61 (65) Python (part 2) UNIVERSITY OF BORÅS
The Hand Class ├── main.py
• Let’s add another module hand.py to the poker package. └── poker
• Create a hand.py file (module) in the poker folder (package) ├── card.py
• import the Deck class from the deck.py module ├── deck.py
• Notice the relative path .deck, i.e. poker/deck.py ├── hand.py
• Define a Hand class in hand.py └── __init__.py
• Notice the class header is defined as class Hand(Deck)
• This is how inheritance works in Python, where Hand inherits from Deck
from .deck import Deck hand.py
• Deck is called the superclass and Hand is called the subclass
class Hand(Deck):
• This means that Hand inherits all attributes and methods from Deck """Represents a hand of playing cards."""
• Public and protected attributes and methods in Deck are available in Hand def __init__(self, label=''):
• The constructor takes an optional parameter label of type str super().__init__(label)
self._label = label
• We know it’s optional since it is assigned an initial value label='' self._cards = []
def __str__(self):
res = [self.label]
for card in self._cards:
res.append(str(card))
return '\n'.join(res)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 62 (65) Python (part 2) UNIVERSITY OF BORÅS
The Hand Class in Action
from poker.card import Card main.py > python main.py terminal from .deck import Deck hand.py
from poker.deck import Deck
from poker.hand import Hand class Hand(Deck):
"""Represents a hand of playing cards."""
hand = Hand('player 1')
print(hand) def __init__(self, label=''):
print() player 1 super().__init__(label)
self._label = label
deck = Deck() self._cards = []
deck.move_cards(hand, 1)
print(hand) player 1 @property
Ace of Spades def label(self):
"""Getter for label"""
return self._label
def __str__(self):
res = [self.label]
for card in self._cards:
res.append(str(card))
return '\n'.join(res)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 63 (65) Python (part 2) UNIVERSITY OF BORÅS
The BridgeHand Class ├── main.py
└── poker
• Let’s add another module bridgehand.py to the poker package.
├── bridgehand.py
• Create a bridgehand.py file (module) in the poker folder (package)
├── card.py
• import the Card class from the card.py module
├── deck.py
• Notice the relative path .card, i.e. poker/card.py
• import the Hand class from the hard.py module
├── hand.py
• Notice the relative path .hand, i.e. poker/hand.py
└── __init__.py
• Define a BridgeHand class in bridgehand.py
• Notice the class header is defined as class BridgeHand(Hand) from .card import Card bridgehand.py
• BridgeHand inherits from Hand from .hand import Hand
• The constructor takes an optional parameter label of type str class BridgeHand(Hand):
"""Represents a bridge hand."""
• The constructor calls the superclass’ constructor
super().__init__(label) hcp_dict = {'Ace': 4,'King': 3,'Queen': 2, Jack': 1}
• The attribute hcp_dict is a class attribute of type dict with bridge def __init__(self, label=''):
points for the high rank cards super().__init__(label)
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 64 (65) Python (part 2) UNIVERSITY OF BORÅS
The BridgeHand Class in Action
from poker.card import Card main.py > python main.py terminal from .card import Card bridgehand.py
from poker.deck import Deck from .hand import Hand
from poker.hand import Hand
from paker.bridgehand import BridgeHand class BridgeHand(Hand):
"""Represents a bridge hand."""
hand = BridgeHand('player 2')
print(hand) hcp_dict = {'Ace': 4,'King': 3,'Queen': 2, Jack': 1}
print() player 2
def __init__(self, label=''):
deck = Deck() player 2 super().__init__(label)
deck.shuffle() Ace of Spades
deck.move_cards(hand, 5) Jack of Clubs def high_card_point_count(self):
print(hand) 6 of Hearts count = 0
print() 5 of Clubs for card in self._cards:
8 of Diamonds rank_name = Card.rank_names[card.rank]
count += BridgeHand.hcp_dict.get(rank_name, 0)
print(hand.high_card_point_count()) 5 return count
Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 65 (65) Python (part 2) UNIVERSITY OF BORÅS