Object-Oriented Programming (part 2/2)#
Python Programming for Engineers#
Tel-Aviv University / 0509-1820 / Fall 2025-2026#
Agenda: Object-Oriented Programming (OOP)#
Building classes
Exam question: TRange
class TRange:
def __init__(self, min_temp, max_temp):
self.min_temp = min_temp
self.max_temp = max_temp
def __repr__(self):
return f"<{self.min_temp},{self.max_temp}>" # <" + str(self.min_temp) + "," + str(self.max_temp) + ">"
print(TRange(10,20))
<10,20>
class TRange:
def __init__(self, min_temp, max_temp):
self.min_temp = min_temp
self.max_temp = max_temp
def __repr__(self):
return "<" + str(self.min_temp) + "," + str(self.max_temp) + ">"
def intersect(self, other):
min_temp_result = max(self.min_temp, other.min_temp)
max_temp_result = min(self.max_temp, other.max_temp)
if min_temp_result > max_temp_result:
print('No intersection!')
return
return TRange(min_temp_result, max_temp_result)
(6): What will be printed?#
t1=TRange(10,20)
t2=TRange(15,25)
print(t1.intersect(t2))
<15,20>
class Machine:
def __init__(self, unsorted_parts):
self.parts = []
last_part_max_temp = -1
next_part = None
for index in range(len(unsorted_parts)):
next_max_temp = 101
for part in unsorted_parts:
# Analyze all parts that have not been sorted yet
if part.temps.max_temp > last_part_max_temp and \
part.temps.max_temp < next_max_temp:
next_max_temp = part.temps.max_temp
next_part = part
self.parts.append(next_part)
last_part_max_temp = next_part.temps.max_temp
The implementation of Part class is not part of the question…#
class Part:
def __init__(self, name, temps):
self.name=name
self.temps=temps
def __repr__(self):
return self.name
(7): What will be printed?#
p_high=Part("p1", TRange(10,20))
p_medium=Part("p2", TRange(15,25))
p_low=Part("p3", TRange(5,7))
parts=[p_high,p_medium,p_low]
m=Machine(parts)
print(m.parts)
[p3, p1, p2]
Self Learning#
Questions from previous exams#
Open Exam 2023-2024 semester A Moed B and answer question 2.
2.A
class Player:
def __init__(self, name, salary, history):
self.name = name
self.salary = salary
self.titles = {}
if type(history)==dict:
self.titles={k.lower(): v for k,v in history.items()}
else:
for record in history:
title = record[0].lower()
if title not in self.titles:
self.titles[title] = 0
self.titles[title] += 1
def __repr__(self):
return "Name: "+self.name+", Number of titles: "+str(sum(self.titles.values()))
print(Player("messi", 3, [("championship", 2020), ("championship", 2022), ("best_player", 2024)]))
print(Player("ronaldo", 4, {'Championship':2, "cup":3}))
Name: messi, Number of titles: 3
Name: ronaldo, Number of titles: 5
2.B
def evaluate(self, weights={'championship':5, 'best_player':4, "cup":3}):
sm=sum([(weights[title] * self.titles[title])**0.5 for title in self.titles])
return round(sm/self.salary, 2) # round(sm/self.salary, 2)
def __lt__(self, other):
return (self.evaluate(), self.name) < (other.evaluate(), other.name)
Player.evaluate=evaluate
Player.__lt__=__lt__
p1=Player("messi", 3, [("championship", 2020), ("championship", 2022), ("best_player", 2024)])
print(p1.evaluate())
p2=Player("ronaldo", 4, {'championship':2, "cup":3})
print(p2.evaluate())
print(p1<p2)
1.72
1.54
False
2.C
def __add__(self, other):
new_name = self.name + "-" + other.name
new_salary = min(10, self.salary+other.salary)
new_titles = {}
for cur_titles in [self.titles, other.titles]:
for title in cur_titles:
new_titles[title]= new_titles.get(title,0)+cur_titles[title] # default 0 in .get() was missing
return Player(new_name, new_salary, new_titles) # return was missing
Player.__add__=__add__
p1=Player("messi", 4, [("championship", 2010), ("championship", 2011), ("best_player", 2013)])
p2=Player("ronaldo", 3, {'championship':2, "cup":3})
p1+p2
Name: messi-ronaldo, Number of titles: 8
2.D
class Team:
def __init__(self, name, players, salary_threshold):
self.name=name if name.isalpha() else "default"
self.cheap_players=sorted([a for a in players if a.salary<salary_threshold], key=lambda a: a.salary)
self.expensive_players=sorted([a for a in players if a.salary>=salary_threshold], key=lambda a: a.salary)
self.total_value=sum([a.evaluate() for a in players])
def __repr__(self):
return "Name: "+self.name+", Is cheap team: "+("yes" if len(self.cheap_players)>len(self.expensive_players) else "no")
def __contains__(self, player):
return player.name in ([p.name for p in self.cheap_players+self.expensive_players])
p1=Player("messi", 4, [("championship", 2010), ("championship", 2011), ("best_player", 2013)])
p2=Player("ronaldo", 3, {'championship':2, "cup":3})
p3=Player("zehavi", 2, [("championship", 2011)])
p4=Player("peretz", 1, { ("best_player", 2013)})
t1=Team("galacticos", [p1,p4,p3,p2], 3)
print(t1)
t2=Team("macabbi", [p3,p4], 3)
print(t2)
print(p1 in t2)
print(p3 in t2)
Name: galacticos, Is cheap team: no
Name: macabbi, Is cheap team: yes
False
True
2.E
def move_player(self, other, name):
player_to_move=None
for subset in [self.cheap_players, self.expensive_players]:
for i, a in enumerate(subset):
if a.name==name:
player_to_move=subset.pop(i)
if player_to_move is None:
print("Player was not found")
else:
if player_to_move.salary<other.cheap_players[-1].salary:
subset=other.cheap_players
else:
subset=other.expensive_players
subset.append(player_to_move)
subset.sort(key=lambda a: a.salary)
self.total_value-=player_to_move.evaluate()
other.total_value+=player_to_move.evaluate()
Team.move_player=move_player
p1=Player("messi", 4, [("championship", 2010), ("championship", 2011), ("best_player", 2013)])
p2=Player("ronaldo", 3, {'championship':2, "cup":3})
p3=Player("zehavi", 2, [("championship", 2011)])
p4=Player("peretz", 1, { ("best_player", 2013)})
t1=Team("galacticos", [p1,p2], 3.5)
t2=Team("galacticos", [p3,p4], 1.5)
print(t1.cheap_players,'\n',t1.expensive_players,'\n',t1.total_value)
print()
print(t2.cheap_players,'\n',t2.expensive_players,'\n',t2.total_value)
t1.move_player(t2, "messi")
print('\n\n')
print(t1.cheap_players,'\n',t1.expensive_players,'\n',t1.total_value)
print()
print(t2.cheap_players,'\n',t2.expensive_players,'\n',t2.total_value)
[Name: ronaldo, Number of titles: 5]
[Name: messi, Number of titles: 3]
3.34
[Name: peretz, Number of titles: 1]
[Name: zehavi, Number of titles: 1]
3.12
[Name: ronaldo, Number of titles: 5]
[]
2.05
[Name: peretz, Number of titles: 1]
[Name: zehavi, Number of titles: 1, Name: messi, Number of titles: 3]
4.41
Strings’ similarity#
How to measure the distance between to strings of the same length?
We need a function
d(s1, s2)that measures the difference between the stringss1,s2d(s1, s1)==0\(\rightarrow\) (perfectly similar).But what’s the value of
d(s1, s2)? In other words, how far iss1froms2?
Why does it matter?
d can then be used for spell checking – we replace a word with an error with the closest correct word in the dictionary
Hamming distance#
Use the number of positions at which the corresponding symbols are different#
Examples:
“Ricks” and “Sixes” is 3
1011101 and 1001001 is 2
2133798 and 2733196 is 3
def hamming_distance(w1, w2):
if len(w1) != len(w2):
print("cannot measure distance")
return
dist = 0
for i in range(len(w1)):
if w1[i] != w2[i]:
dist += 1
return dist
(8): What will be printed?#
print(hamming_distance('ricks', 'rix'))
cannot measure distance
None
print(hamming_distance('ricks', 'rikxs'))
print(hamming_distance('morty', 'morti'))
print(hamming_distance('summer', 'samarr'))
print(hamming_distance('ricks', 'rix'))
2
1
3
cannot measure distance
None
Gene card#
A gene is a sequence of the letters A,C,G,T
Create a class Gene to represent a gene
Implement__init__,__repr__,__len__:
>>> Gene('A Gc tGTCAa GTC')
AGCTGTCAAGTC
>>> len(Gene('A Gc tGTCAa GTC'))
12
Let’s start with __init__ (constructor)#
Should remove spaces, and convert to upper case
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
(9): What will be printed?#
print(Gene('AGc tGT CAa GTC'))
<__main__.Gene object at 0x7f7e682096d0>
__repr__should return the gene as a string
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
#### New method ####
def __repr__(self):
return self.seq
####################
(10): What will be printed in the last line?#
print(Gene('AGc tGT CAa GTC'))
print(Gene('AGc tGT CAa GTC Ricks'))
AGCTGTCAAGTC
AGCTGTCAAGTCRICKS
Should validate input (only ACGT are allowed).
For invalid input, print a warning message
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
#### New code lines ####
if not self.is_valid():
print('Warning: Invalid sequence!')
########################
def __repr__(self):
return self.seq
#### new method ####
def is_valid(self):
bases = 'ACGT'
for c in self.seq:
if c not in bases:
return False
return True
####################
print(Gene('AGc tGT CAa GTC'))
print(Gene('AGc tGT CAa GTC Ricks'))
AGCTGTCAAGTC
Warning: Invalid sequence!
AGCTGTCAAGTCRICKS
__len__should return the number of letters
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
if not self.is_valid():
print('Warning: Invalid sequence!')
def __repr__(self):
return self.seq
def is_valid(self):
bases = 'ACGT'
for c in self.seq:
if c not in bases:
return False
return True
#### New method ####
def __len__(self):
return len(self.seq)
####################
(11): What will be printed?#
print(Gene('AGc tGT CAa GTC'))
print(len(Gene('AGc tGT CAa GTC')))
AGCTGTCAAGTC
12
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
if not self.is_valid():
print('Warning: Invalid sequence!')
def __repr__(self):
return self.seq
def is_valid(self):
bases = 'ACGT'
for c in self.seq:
if c not in bases:
return False
return True
def __len__(self):
return len(self.seq)
print(Gene('AGc tGT CAa GTC'))
AGCTGTCAAGTC
Implement a distance method that returns Hamming distance between genes#
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
if not self.is_valid():
print('Warning: Invalid sequence!')
def is_valid(self):
bases = 'ACGT'
for c in self.seq:
if c not in bases:
return False
return True
def __repr__(self):
return self.seq
def __len__(self):
return len(self.seq)
#### New method ####
def distance(self, other):
if len(self) != len(other):
print("Cannot measure distance")
return
dist = 0
for i in range(len(self)):
if self.seq[i] != other.seq[i]:
dist += 1
return dist
#####################
(12): What will be printed?#
Gene('AGc tGT CAa GTC').distance(Gene('AGc tGC CAaA TA'))
3
Gene Class – Codons#
Genes are composed of 3-letter codons
is_valid should verify:
The gene contains whole codons
The gene starts with the start codon: ATG
The gene ends with a stop codon: TAG, TAA or TGA
The gene does not contain another stop codon.
#### New method ####
def is_valid(self):
bases = 'ACGT'
start_codon = 'ATG'
stop_codons = ['TAG','TGA','TAA']
# check only legal characters
for c in self.seq:
if c not in bases:
return False
# check length divides by 3
if len(self) % 3 != 0:
return False
# check start codon
if self.seq[:3] != start_codon:
return False
# check stop codon
if self.seq[-3:] not in stop_codons:
return False
# check no stop codons in the middle
for i in range(0, len(self) - 3, 3):
if self.seq[i:i+3] in stop_codons:
return False
# Reached here? seq is valid!!!
return True
####################
class Gene:
def __init__(self, seq):
seq = seq.replace(' ', '')
seq = seq.upper()
self.seq = seq
if not self.is_valid():
print('Warning: Invalid sequence!')
def is_valid(self):
bases = 'ACGT'
for c in self.seq:
if c not in bases:
return False
return True
def __repr__(self):
return self.seq
def __len__(self):
return len(self.seq)
def distance(self, other):
if len(self) != len(other):
print("Cannot measure distance")
return
dist = 0
for i in range(len(self)):
if self.seq[i] != other.seq[i]:
dist += 1
return dist
#### New method ####
def is_valid(self):
bases = 'ACGT'
start_codon = 'ATG'
stop_codons = ['TAG','TGA','TAA']
# check only legal characters
for c in self.seq:
if c not in bases:
return False
# check length divides by 3
if len(self) % 3 != 0:
return False
# check start codon
if self.seq[:3] != start_codon:
return False
# check stop codon
if self.seq[-3:] not in stop_codons:
return False
# check no stop codons in the middle
for i in range(0, len(self) - 3, 3):
if self.seq[i:i+3] in stop_codons:
return False
# Reached here? seq is valid!!!
return True
####################