[go: up one dir, main page]

0% found this document useful (0 votes)
8 views65 pages

L03 - Python (Part 2) - 1

Uploaded by

malcolm.holmen
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views65 pages

L03 - Python (Part 2) - 1

Uploaded by

malcolm.holmen
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 65

UNIVERSITY OF BORÅS

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

• A for loop has:


• a head, consisting of the keyword for, for i in range(2): main.py
print(i)
followed by variable(s), the keyword in,
an iterable object, and a colon : > python main.py terminal
0
• a body, consisting of indented 1
statements.
• The simplest for loop has one loop
for i in range(0,2): main.py
variable and uses the built-in range print(i)
function to return an iterable sequence of > python main.py terminal
numbers between start (inclusive, 0
default 0) and stop (exclusive) using a 1

step size (default 1).


for i in range(0,2,1): main.py
range(stop) print(i)
range(start, stop)
range(start, stop, step) > python main.py terminal
0
1

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

print(i, end=' ')

> python main.py terminal

0 1 2

• The iterable can also be a sequence of characters in a string. for letter in 'Gadsby': main.py

print(letter, end=' ')

> python main.py terminal

G a d s b y

def has_e(word): main.py


• We can process each character in a string using a for loop.
for letter in word:
if letter == 'E' or letter == 'e':
return True
return False

print( has_e('Gadsby') )
print( has_e('Emma') )

> python main.py terminal

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

next element in a sequence. if 'E' in word or 'e' in word:


return True
word = 'Gadsby' else:
return False
for letter in word
print( has_e('Gadsby') )
• The in operator can also be used to check if an element exists print( has_e('Emma') )
in a sequence.
> python main.py terminal

False
word = 'Gadsby' True
'e' in word # False

def has_e(word): main.py

return 'E' in word or 'e' in word

print( has_e('Gadsby') )
print( has_e('Emma') )

> python main.py terminal

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

fruit = 'banana' letter = fruit[0]


print(letter)
• You can select a character from a string with an index inside the bracket operator [].
letter = fruit[1]
letter = fruit[1] # letter contains the character 'a' print(letter)
• Python uses zero-based indexing into sequences, i.e. the first element has index 0. print( len(fruit) )

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)

letter = fruit[len(fruit)-1] # letter contains the character 'a' terminal


> python main.py
'b'
• Python permits negative indexing into sequences, where index -1 is the last element, -2 the next 'a'
to last, etc. 6
'a'
letter = fruit[-2] # letter contains the character 'n' 'a'
'n'

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)

fruit = 'banana' > python main.py terminal


slice = fruit[3:] # slice contains 'ana' 'ban'
'ana'
• We can also use negative indexes. 'ban'
'ana'
'nan'
fruit = 'banana'
slice = fruit[2:-1] # slice contains 'nan'

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

string. greeting[0] = 'J'

• 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

an existing string. new_greeting = 'J' + greeting[1:]


print(new_greeting)

> python main.py terminal


'Jello, world!'

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

two strings are equal, we can use the equality operator.


if word == 'banana':
print('All right, banana.')

> python main.py terminal


All right, banana.

• 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')

> python main.py terminal


apple comes before banana.
Pineapple comes before banana.

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

• A method is similar to a function, but is part of a string instance, and is


therefore called from a string instance using the dot operator . new_word = word.upper()
print(new_word)
• E.g. the method upper returns a new string with all uppercase letters,
whereas the method lower returns a new string with all lowercase letters. new_word = word.lower()
print(new_word)

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)

lst = [42.0, 123.5]


empty_list = [] print(lst)
list_of_integers = [42, 123]
lst = ['Cheddar', 'Edam', 'Gouda']
list_of_floats = [42.0, 123.5] print(lst)
list_of_strings = ['Cheddar', 'Edam', 'Gouda']
list_of_mixed_types = ['spam', 2.0, 5] lst = ['spam', 2.0, 5]
print(lst)
• A list can also contain another list (a nested list). lst = ['spam', 2.0, 5, [10, 20]]
print(lst)
list_with_nested_list = ['spam', 2.0, 5, [10, 20]] print( len(lst) )

> python main.py terminal


• The len function returns the length of a list (number of elements). []
[42, 123]
len([10, 20, 30]) # a list of length 3 [42.0, 123.5]
['Cheddar', 'Edam', 'Gouda']
['spam', 2.0, 5]
['spam', 2.0, 5, [10, 20]]
4

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

an index, where the index of the first element is 0. cheese = cheeses[0]


print(cheese)

cheeses = ['Cheddar', 'Edam', 'Gouda'] numbers = [42, 123]


cheese = cheeses[0] # cheese contains 'Cheddar' numbers[1] = 17
print(numbers)
• Unlike strings, lists are mutable, so the bracket operator [] with an
index can assign a new value to an element. numbers = [42, 123]
number = numbers[-1]
numbers = [42, 123] print(number)
numbers[1] = 17 # numbers contains [42, 17]
cheeses = ['Cheddar', 'Edam', 'Gouda']
print( 'Edam' in cheeses )
• Negative indexing works with lists.
> python main.py terminal
numbers = [42, 123]
'Cheddar'
number = numbers[-1] # number contains 123 [42, 17]
123
• The in operator works on lists, e.g. to check if a given element True
appears anywhere in the list.

cheeses = ['Cheddar', 'Edam', 'Gouda']


'Edam' in cheeses # True

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

letters = ['a', 'b', 'c', 'd']


letters = ['a', 'b', 'c', 'd'] print(letters)
slice = letters[1:3] # slice contains ['b', 'c']
print( letters[1:3] )
• If you omit the first index, the slice starts at the beginning. print( letters[:2] )
print( letters[2:] )
slice = letters[:2] # slice contains ['a', 'b']
copy = letters[:]
• If you omit the second index, the slice goes to the end. print(copy)

copy = list(letters)
slice = letters[2:] # slice contains ['c', 'd'] print(copy)

• If you omit both, the slice is a copy of the whole list.


> python main.py terminal
copy = letters[:] # copy contains ['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd']
['b', 'c']
• Another way to copy a list is to use the list function. ['a', 'b']
['c', 'd']
copy = list(letters) # copy contains ['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd']
• Since list is a built-in function, avoid using list as a variable name.

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)

lst = ['spam'] * 4 # lst is ['spam', 'spam', 'spam', 'spam'] smallest = min(t1)


print(smallest)
• The built-in function sum adds up the elements in a list. largest = max(t2)
print(largest)
total = sum([1, 2]) # total is 3
> python main.py terminal

• 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')

t = ['a', 'b', 'c'] terminal


> python main.py
t.remove('b') # t contains ['a', 'c']
['a', 'b', 'c', 'd', 'e']
t.remove('d') # ValueError
['a', 'b', 'c', 'd', 'e', 'f', 'g']
'b'
['a', 'c']
['a', 'c']
ValueError: list.remove(x): x not in list

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'

• We can check if two strings:


• have the same value with the equality operator == main.py
a = 'banana'
• are referring to the same object with the is operator b = 'banana'

a == b # True if a and b have the same value print( a == b )


a is b # True if a and b are referring to the same object print( a is b )

• 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]

• We can check if two lists:


• have the same value with the equality operator == main.py
a = [1, 2, 3]
• are referring to the same object with the is operator b = [1, 2, 3]

a == b # True if a and b have the same value print( a == b )


a is b # True if a and b are referring to the same object print( a is b )

• 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.

• For immutable objects like strings, aliasing is not a problem.

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)

def pop_first(lst): letters = ['a', 'b', 'c']


return lst.pop(0) print(letters)

letter = pop_first(letters)
letters = ['a', 'b', 'c'] print(letter)
letter = pop_first(letters) print(letters)

> python main.py terminal


• If the function modifies the list, its modifying the original list passed by the caller. ['a', 'b', 'c']
'a'
print(letter) # 'a' ['b', 'c']
print(letters) # ['b', 'c']

• 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 {}

numbers = {} # numbers is an empty dictionary {} numbers['zero'] = 0


print(numbers)
• To add item to a dictionary, we use an key within square brackets [] and assign a value.
numbers['one'] = 1
numbers['zero'] = 0 # 'zero' is a key, 0 is a value
numbers['two'] = 2
print(numbers)
• The item represents an association (mapping) of a key and a value, expressed as key: value
number = numbers['two']
print(numbers) # {'zero': 0} one item with key 'zero' and value 0
print(number)

• We can add more items. n_items = len(numbers)


numbers['one'] = 1 # {'zero': 0, 'one': 1} print(n_items)
numbers['two'] = 2 # {'zero': 0, 'one': 1, 'two': 2}
numbers['three']
• To look up a key and get the corresponding value, we use the key within the bracket operator [].
If the key isn’t in the dictionary, we get a KeyError. > python main.py terminal
{}
number = numbers['two'] # number contains 2
numbers['three'] # KeyError {'zero': 0}
{'zero': 0, 'one': 1, 'two': 2}
2
• The len function returns the number of items in a dictionary.
3
len(numbers) # 3 KeyError: 'three'

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)

> python main.py terminal


• Dictionaries are mutable, but can be copied with the dict function.
{}
{'zero': 0, 'one': 1, 'two': 2}
numbers_copy = dict(numbers)
{'zero': 0, 'one': 1, 'two': 2}
{'zero': 0, 'one': 1, 'two': 2}
• An empty dictionary can also be created using the dict function. {}

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.

numbers = {'zero': 0, 'one': 1, 'two': 2} > python main.py terminal


key_exits = 1 in numbers.values() # True {'zero': 0, 'one': 1, 'two': 2}
key_exits = 3 in numbers.values() # False True
False
True
False

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)

• We can use the items method to traverse the items, where


two loop variables are used; one for the key, the other for the
value.
for key, value in counter.items():
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)

• You can put a dictionary in a dictionary as a value. key = 4


value = {'r':1, 'o':2, 'u':3, 's':4}
key = 4 d = {key: value}
value = {'r':1, 'o':2, 'u':3, 's':4} print(d)
d = {key: value}
key = ['r', 'o', 'u', 's']
• You can’t put a list or a dictionary in a dictionary as a key (results in a TypeError). # key = {'r':1, 'o':2, 'u':3, 's':4}
value = 4
key = ['r', 'o', 'u', 's'] # or {'r':1, 'o':2, 'u':3, 's':4] d = {key: value}
value = 4
d = {key: value}
> python main.py terminal
• Since dictionaries use hash tables, the keys have to be hashable. {4: ['r', 'o', 'u', 's']}
{4: {'r':1, 'o':2, 'u':3, 's':4}}
• A hash is a function that takes a value (of any kind) and returns an integer. TypeError: unhashable type: 'list'
Dictionaries use these integers, called hash values, to store and look up keys.

• 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) )

t = ('l', 'u', 'p', 'i', 'n') # t is a tuple t = 'p',


print(t)
print( type(t) )
• To create a tuple with a single element, you have to include a final comma. terminal
t = ('p',) > python main.py
t = ('p',) # t is a tuple print(t) ('l', 'u', 'p', 'i', 'n')
print( type(t) ) <class: 'tuple'>
• A single value in parentheses is not a tuple. ('l', 'u', 'p', 'i', 'n')
t = ('p') <class: 'tuple'>
print(t) ('p')
t = ('p') # t is a string print( type(t) ) <class: 'tuple'>
('p')
• You can create a tuple with the built-in function tuple. t = tuple() <class: 'tuple'>
print(t) p
t = tuple() # t is an empty tuple print( type(t) ) <class: 'str'>
t = tuple('l', 'u', 'p', 'i', 'n') ()
t = tuple('lupin') <class: 'tuple'>
print(t) ('l', 'u', 'p', 'i', 'n')
print( type(t) ) <class: 'tuple'>

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)

t = tuple('lup') + ('i', 'n') # ('l','u','p','i','n') t = tuple('spam') * 2


print(t)

• The * operator duplicates a tuple a given number of times. t = sorted(tuple('lupin'))


print(t)
t = tuple('spam') * 2 # ('s','p','a','m','s','p','a','m')
> python main.py terminal
• Using the sorted function on a tuple, returns a sorted list. ('l', 'u', 'p', 'i', 'n')
l
t = sorted(tuple('lupin')) # ['i','l','n','p','u'] ('u', 'p')
('l’, 'u', 'p', 'i', 'n')
('s', 'p', 'a', 'm', 's', 'p', 'a', 'm')
['i', 'l', 'n', 'p', 'u']

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 = ('l', 'u', 'p', 'i', 'n') > python main.py terminal

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 = d[(1, 2)] # value contains 3 key = (3, 4)


key = (3, 4) value = d[key]
value = d[key] # value contains 7 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 # same as (a, b) = (1, 2) (a, b) = (1, 2)


print(a, b)
• If the left side of an assignment is a tuple, the right side can be any sequence.
username, domain = ['monty', 'python.org']
username, domain = ['monty', 'python.org'] print(username, domain)

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)

def min_max(lst): lst = [2, 4, 1, 3]


return min(lst), max(lst) print(lst)

lst = [2, 4, 1, 3] low, high = min_max(lst)


low, high = min_max(lst) # low contains 1, high contains 4 print(low, high)

• 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)

lst = [1, 2] > python main.py terminal

sum = add(*lst) # lst [1,2] unpacked to two values 1 and 2 [2, 4, 1, 3]


14
2.0
3

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]

scores1 = [1, 2, 4, 5] z = zip(scores1, scores2)


scores2 = [5, 5, 2, 2] print(z)
z = zip(scores1, scores2) # z is a zip object <zip at ...>
for score1, score2 in zip(scores1, scores2):
• We can use the zip object to loop through the values in the two sequences pairwise. print(score1, score2)

for index, score1 in enumerate(scores1):


for score1, score2 in zip(scores1, scores2):
print(index, score1)
print(score1, score2)

# 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}

s.discard(10) # {1, 3, 4, 5} {1, 2, 3, 4, 5}

{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

• Difference. print(a ^ b) # symmetric difference

a - b # {1, 2} 3 in a # membership testing

• 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'>

AttributeError: 'frozenset' object has no attribute 'add'

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]

• A list comprehension can also have an if clause that determines which


elements are included in the list.
words = ['level\n', 'car\n', 'madam\n' ,'dog\n'] main.py
numbers = [5, -5, 7, -7]
print(words)
positive_numbers = [n for n in numbers if n > 0]
word_list = [word.strip() for word in words]
print(word_list)

numbers = [5, -5, 7, -7]


print(numbers)

positive_numbers = [n for n in numbers if n > 0]


print(positive_numbers)

> python main.py terminal


['level\n', 'car\n', 'madam\n', 'dog\n']
['level', 'car', 'madam', 'dog']
[5, -5, 7, -7]
[5, 7]

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)}

• A dictionary comprehension can also have an if clause that determines


which items are included in the dictionary.
words = ['level', 'car', 'madam' ,'dog'] main.py
print(words)
words = ['level', 'car', 'madam', 'dog']
word_dict = {key:value word_dict = {key:value for key, value in enumerate(words)}
for key, value in enumerate(words) print(word_dict)
if 'a' in value}
word_dict = {key:value for key, value in enumerate(words)
if 'a' in value}
print(word_dict)

> python main.py terminal


['level', 'car', 'madam', 'dog']
{0: 'level', 1: 'car', 2: 'madam', 3: 'dog'}
{1: 'car', 2: 'madam'}

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.

import os cwd = os.getcwd()


print(cwd)

• 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)

os.path.abspath('memo.txt') folder_exists = os.path.exists('.conda')


print(folder_exists)
• The listdir function takes a path to a directory and returns a listing of files and subdirectories.
is_file = os.path.isfile('memo.txt')
os.listdir(cwd) is_dir = os.path.isdir('data')
print(is_file)
print(is_dir)
• The os.path.exists function checks if a file or directory exists.

os.path.exists('.conda') python_interpreter = os.path.join(cwd,'data','a.csv')


print(python_interpreter)
• The os.path.isfile and os.path.isdir functions check if a path is to a file or directory. > python main.py terminal
/home/patrick
os.path.isfile('memo.txt') /home/patrick/memo.txt
os.path.isdir('.conda')
['main.py', 'memo.txt', 'data', '.conda']
True
• The os.path.join function joins directory and filenames into a path. True
True
os.path.join(cwd, 'data', 'a.csv') /home/patrick/data/a.csv

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")

f = open('image.jpg', 'rb') with open('example.txt', 'a') as f:


f.write("!\n")
• To close a file, the close method is called on the file handle variable.
with open('example.txt', 'r') as f:
f = open('example.txt', 'r') contents = f.read()
f.close() print(contents)
• The with construct automatically closes the file when done by calling close on the file handle.
with open('example.txt', 'r') as f:
with open('example.txt', 'r') as f: for line in f:
# process file here print(line.strip())
# file automatically closed here
> python main.py with open('example.txt', 'r') as f:
• The file handle contains numerous methods: Hello lines = f.readlines()
World print(lines)
• f.write('Hello\n') # writes/appends to the file
!
• f.read() # reads the file’s contents with open('image.jpg', 'rb') as f:
Hello image = f.read()
• f.readlines() # returns a list with the file’s lines World
! with open('copy.jpg', 'wb') as f:
• for line in f: # processes the file line by line f.write(image)
['Hello\n', 'World\n', '!\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.

s = f'I have spotted 23 camels'

• 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'

• Multiple placeholders with expressions be used in an f-string.

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

s = f'I have spotted {num_camels} camels'


print(s)

s = f'In {num_years} years I have spotted {num_camels} camels'


print(s)

> python main.py terminal


'I have spotted 23 camels'
'In 1.5 years I have spotted 23 camels'

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'

• The expressions can include lists, dictionaries, and other types.

t = [1, 2, 3]
d = {'one': 1}
s = f'Here is a list {t} and a dictionary {d}'

num_years = 1.5 main.py


num_camels = 23

line = f'In {round(num_years * 12)} months I have spotted {num_camels} camels'


print(line)

t = [1, 2, 3]
d = {'one': 1}
s = f'Here is a list {t} and a dictionary {d}'
print(s)

> python main.py terminal


'In 18 months I have spotted 23 camels'
"Here is a list [1, 2, 3] and a dictionary {'one': 1}"

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.

class Card: main.py


"""Represents a standard playing card.""" docstring within
tripple quotes class Card:
"""Represents a standard playing card."""
• To create a new instance (object) of a class, the class name is followed by parentheses ().
The new instance (object) is usually assigned to a variable. card = Card()

card = Card() print( type(card) )


print(card)
• The new object is of type __main__.Card, where __main__ is the name of the module in
which Card is defined.

print(type(card)) # <class '__main__.Card'>


> python main.py terminal
<class '__main__.Card'>
• When you print an object, Python tells you what type it is and where (memory <__main__.Card object at 0x709cfc23c2f0>
address) it is stored in memory (the 0x prefix means its a hexadecimal number).

print(card) # <__main__.Card object at 0x709cfc23c2f0>

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())

card = Card('Spades', 'Ace') # create an object passing in arguments


> python main.py terminal
• A class can contain additional (normal) methods besides the special constructor method (first parameter is self).
<class '__main__.Card'>
def to_tuple(self): # first parameter is self, which makes this an instance method <__main__.Card object at 0x709cfc23c2f0>
return (self.suit, self.rank) # returns a tuple ('Spades', 'Ace') Spades Ace
('Spades', 'Ace')
• Once we have an instance (object) of a class, we can access the attributes and methods using dot notation .

card = Card('Spades', 'Ace')


print(card.suit, card.rank) # prints out Spades Ace
print(card.to_tuple()) # prints out ['Spades', 'Ace']

Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 53 (65) Python (part 2) UNIVERSITY OF BORÅS
Attributes and Methods class Card:
main.py

"""Represents a standard playing card."""

• 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

suit_names = ['Clubs', 'Diamonds',


'Hearts', 'Spades']

• 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']

• A constructor __init__ is a special method def __init__(self, suit, rank):


self.__suit = suit
• Other special methods are: self.__rank = rank

• __str__ which returns a string @property


representation of an instance (object), def suit(self):
used by e.g. the print function return self.__suit

card1 = Card(0, 2) # 'Clubs', '2' @property


print(card1) # 'Clubs', '2' def rank(self):
return self.__rank
• __eq__ which overloads the == operator, taking
another object O, returning True if self == O def to_tuple(self):
return (self.suit, self.rank)
• __lt__ which overrides the < operator, taking
def __str__(self):
another object O, returning True if self < O
rank_name = Card.rank_names[self.__rank]
suit_name = Card.suit_names[self.__suit]
• __le__ which overrides the <= operator, taking return f'{rank_name} of {suit_name}'
another object O, returning True if self <= O
def __eq__(self, other):
• self refers to the current object, where the last 3 return isinstance(other, Card) and self.__suit == other.suit and self.__rank == other.rank
methods override (redefine) operators for the class,
such that 2 objects of a class can be compared using the def __lt__(self, other):
operators. return isinstance(other, Card) and self.to_tuple() < other.to_tuple()
card1 = Card(0, 2) # 'Clubs', '2' def __le__(self, other):
card2 = Card(0, 3) # 'Clubs', '3' return isinstance(other, Card) and self.to_tuple() <= other.to_tuple()
card1 < card2 # True using __lt__
card1, card2 = Card(0, 2), Card(0, 3)
• There are more special methods, e.g. __gt__ for > and print(card1 < card2) > python main.py terminal
__ge__ for >=, but it suffices to override __lt_ and print(card1) True
__le__ 2 of Clubs

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

card1 = Card(0, 2) card1 = Card(0, 2)


print(type(card)) print(type(card))

> python main.py terminal


<class 'poker.card.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

print(Card.suit_names) ['Clubs', 'Diamonds', 'Hearts', 'Spades']


print(Card.rank_names) [None, 'Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']

print(Card.suit_names[0]) Clubs class Card: card.py


print(Card.rank_names[11]) Jack suit_names = ['Clubs','Diamonds','Hearts','Spades']
rank_names = [None,'Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King','Ace']
queen = Card(1, 12)
queen2 = Card(1, 12) def __init__(self, suit, rank):
six = Card(1, 6) self.__suit = suit
print(queen) Queen of Diamonds self.__rank = rank
print(queen2) Queen of Diamonds
print(six) 6 of Diamonds @property
def suit(self):
print(queen is queen2) False return self.__suit
print(queen is six) False
@property
print(queen == queen2) True def rank(self):
print(queen == six) False return self.__rank
print(queen != six) True
print(queen != queen2) False def to_tuple(self):
return (self.suit, self.rank)
print(six < queen) True
print(six > queen) False def __str__(self):
print(queen < queen2) False rank_name = Card.rank_names[self.__rank]
print(queen > queen2) False suit_name = Card.suit_names[self.__suit]
return f'{rank_name} of {suit_name}'
print(queen <= queen2) True
print(queen <= six) False def __eq__(self, other):
print(queen >= six) True return isinstance(other, Card) and self.__suit == other.suit and self.__rank == other.rank

def __lt__(self, other):


return isinstance(other, Card) and self.to_tuple() < other.to_tuple()

def __le__(self, other):


main.py return isinstance(other, Card) and self.to_tuple() <= other.to_tuple()

Patrick Gabrielsson Data Visualization (C1VI1B) Autumn 2025 59 (65) Python (part 2) UNIVERSITY OF BORÅS
import random deck.py

The Deck Class


from .card import Card

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 package random (for random numbers, etc.)


└── poker return self._cards

• 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

The Deck Class in Action


from .card import Card

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 = []

• The constructor calls the superclass’ constructor super().__init__(label) @property


def label(self):
• This will initialize the attributes in Deck, which are also available in Hand """Getter for label"""
• The special method __str__ returns a string representation of a Deck object. 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 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)

• The method high_card_point_count returns the bridge points def high_card_point_count(self):


awarded for the current BridgeHand object count = 0
for card in self._cards:
rank_name = Card.rank_names[card.rank]
count += BridgeHand.hcp_dict.get(rank_name, 0)
return count

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

You might also like