Module 2, Practical 1
The practicals of the second teaching module are a refinement of those prepared by Massimiliano Luca and Erik Dassi. Many thanks for their help.
Object Oriented Programming
As seen in the lecture, Python is a multi-paradigm language and it supports in fact the imperative/procedural paradigm (programs are sequences of statements that change the state of the system), the functional paradigm (programs are seen as mathematical functions, e.g. list comprehensions), some libraries are declarative (they define the logic without specifying the control-flow e.g. Matplotlib) but is also Object Oriented. In fact everything in Python is an object. Moreover, as we will see, new data-types can be defined in Python.
In Object Oriented Programming (OOP) objects are data structures that contain data, which is attributes and functions to work with them. In OOP, programs are made by a set of objects that interact with each other.
OOP allows to create a distinction (abstraction) between the way objects are implemented and how objects are used (i.e. what we can do with them).
Classes, Methods and Objects
The three key players of OOP are: classes, objects and methods.
Classes (types) are an abstraction that captures:
the internal data representation (i.e. data attributes that are called fields)
the interface to interact with the class (i.e. functions that can be used to manipulate the the methods).
Objects are instances of classes. Classes define the structure and are used to create objects. Objects are a concrete realization of the class, a real instance based on the footprint of the class. Programs are interactions among different objects.
Methods are functions that can be applied to manipulate objects.
Attributes and methods within an instantiated object can be accessed by using the . (dot) operator.
Self
Within a class method, we can refer to that very same instance of the object being created by using a special argument that is called self. self is always the first argument of each method.
Important note: All data types seen so far are in fact classes and every time that we used a data type (e.g. defining a list, a string etc.) we were in fact instantiating an object of that type (class).
Definition of a class
The syntax to define a class is the following:
class class_name:
#the initializer method
def __init__(self, val1,...,valn):
self.att1 = val1
...
self.attn = valn
#definition of a method returning something
def method1(self, par1,...,parn):
...
return value
#definition of a method returning None
def method2(self, par1,...,parn):
...
In this case we defined a class class_name that has att1,..., attn (attributes) fields and two methods method1 with parameters par1,...,parn returning a value value and a method method2 with parameters par1,...,parn that does not return anything.
The values of the fields are initialized when the object is instantiated at the beginning by calling the __init__ method, which does not return anything. Note also the use of self in the initializer, which is used to specify that each field of this instance has to be given the corresponding value and must always be the first argument of the initializer.
The object is instantiated with:
my_class = class_name(p1,...,pn)
which attributes the values p1,...,pn to the fields field1,...,fieldn.
Example: Let’s define a simple class rectangle with two fields (length and width) and two methods (perimeter and area).
[ ]:
class class_name:
#the initilizer method
def __init__(self, val1,...,valn):
self.att1 = val1
...
self.attn = valn
#definition of a method returning something
def method1(self, par1,...,parn):
...
return value
#definition of a method returning None
def method2(self, par1,...,parn):
...
my_class = class_name(p1,...,pn)
[2]:
import math
class Rectangle:
def __init__(self, l,w):
self.length = l
self.width = w
def perimeter(self):
return 2*(self.length + self.width)
def area(self):
return self.length * self.width
def diagonal(self):
return math.sqrt(self.length**2 + self.width**2)
R = Rectangle(5,10)
print(type(R))
R1 = Rectangle(5,10)
print(type(R1))
print("R == R1? {} id R:{} id R1:{}".format(R == R1,
id(R),
id(R1)))
p = R.perimeter()
a = R.area()
d = R.diagonal()
print("\nR:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R.length,
R.width,
p,
a))
print("R's diagonal: {:.2f}".format(d))
R2 = Rectangle(72,13)
p = R2.perimeter()
a = R2.area()
d = R2.diagonal()
print("\nR2:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R2.length,
R2.width,
p,
a))
print("R's diagonal: {:.2f}".format(d))
<class '__main__.Rectangle'>
<class '__main__.Rectangle'>
R == R1? False id R:140436373140688 id R1:140436373142480
R:
Length: 5 Width: 10
Perimeter: 30
Area:50
R's diagonal: 11.18
R2:
Length: 72 Width: 13
Perimeter: 170
Area:936
R's diagonal: 73.16
Note that the type of the two objects R and R2 are of type Rectangle and that they have different identifiers. Instantiating objects automatically calls the initializer methods (__init__) passing the correct parameters to it. The dot . operator is used to access methods of the objects. Through the dot operator we can also access the fields of an object, even though this is normally not the best practice and implementing methods to get and set the values of fields are
recommended.
The life-cycle of classes and objects in a program:
The usual life-cycle of classes and objects is the following:
Classes are defined with the specification of class attributes (fields) and methods;
Objects are instantiated based on the definition of the corresponding classes;
Objects interact with each other to implement the logic of the program and modify their state;
Objects are destroyed (explicitly with del) or implicitly when there are no more references to them.
Encapsulation
When defining classes, it is possible to hide some of the details that must be kept private to the object itself and not accessed directly. This can be done by setting methods and attributes (fields) as private to the object (i.e. accessible only internally to the object itself).
Private attributes and methods can be defined using the __ notation (i.e. the name of the attribute or method is preceded by two underscores __).
Example Let’s see what happens to the rectangle class with encapsulation.
[3]:
import math
class Rectangle:
def __init__(self, l,w):
self.__length = l
self.__width = w
def perimeter(self):
return 2*(self.__length + self.__width)
def area(self):
return self.__length * self.__width
def diagonal(self):
return math.sqrt(self.__length**2 + self.__width**2)
R = Rectangle(10,6)
p = R.perimeter()
a = R.area()
d = R.diagonal()
#we might be tempted to access the encapsulated values:
print("\nR:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R.__length,
R.__width,
p,
a))
#The following is going to fail alike.
#print("\nR:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R.length,
# R.width,
# p,
# a))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[3], line 22
20 d = R.diagonal()
21 #we might be tempted to access the encapsulated values:
---> 22 print("\nR:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R.__length,
23 R.__width,
24 p,
25 a))
AttributeError: 'Rectangle' object has no attribute '__length'
Since the length and width attributes are private to the class, it is not possible to get access from the outside. The code above will fail with the object has no attribute error message
To work around this, we can define a specific interface to access the values (these are normally called getter methods as they get and return the value).
Example Let’s see what happens to the rectangle class with encapsulation and getter methods.
[4]:
import math
class Rectangle:
def __init__(self, l,w):
self.__length = l
self.__width = w
def getLength(self):
return self.__length
def getWidth(self):
return self.__width
def perimeter(self):
return 2*(self.__length + self.__width)
def area(self):
return self.__length * self.__width
def diagonal(self):
return math.sqrt(self.__length**2 + self.__width**2)
R = Rectangle(10,6)
p = R.perimeter()
a = R.area()
d = R.diagonal()
print("\nR:\nLength: {} Width: {}\nPerimeter: {}\nArea:{}".format(R.getLength(),
R.getWidth(),
p,
a))
R:
Length: 10 Width: 6
Perimeter: 32
Area:60
Setter methods can be used to change the values of attributes after initialization.
Example: Let’s define a Person class with the following attributes: name, surname, telephone number and address. All attributes are private. The address and phone numbers might change, so we need a method to change them.
[5]:
class Person:
def __init__(self, name, surname, birthdate):
self.__n = name
self.__s = surname
self.__dob = birthdate
self.__a = "unknown"
self.__t = "unknown"
def setAddress(self, address):
self.__a = address
def setTelephone(self, telephone):
self.__t = telephone
def getName(self):
return self.__n
def getSurname(self):
return self.__s
def getDoB(self):
return self.__dob
def getAddress(self):
return self.__a
def getTel(self):
return self.__t
Joe = Person("Joe", "Page", "20/5/1980")
Joe.setAddress("Somerset Rd.,Los Angeles, CA 90016")
print("{} {}\nDate of Birth: {}\nPhone: {}\nAddress: {}".format(Joe.getName(),
Joe.getSurname(),
Joe.getDoB(),
Joe.getTel(),
Joe.getAddress()
))
#Joe moves to Trento
Joe.setAddress("via Sommarive, Povo, Trento")
print("\nNew address: {}".format(Joe.getAddress()))
Joe Page
Date of Birth: 20/5/1980
Phone: unknown
Address: Somerset Rd.,Los Angeles, CA 90016
New address: via Sommarive, Povo, Trento
The setAddress method is a setter method that is used to change the value of the attribute __a.
Special methods
As seen in the lecture, it is possible to redefine some operators by redefining the corresponding special methods through a process called overriding.
These are useful to define things like the sum of two objects (__add__), the equality (__eq__), which one is the smallest (__lt__ that is less than) or the way the object should be translated into a string (__str__), for example for printing purposes.
More information on these special methods can be found here.
Example A decimal number \(T\) can be expressed in base \(X\) (for simplicity we will consider \(X \in [1,9]\)) as: \(T = aX^{N} + bX^{N-1}+...+ (n+1) X^{0}\). Such a number can be represented as two elements: \((X, (a,b,...,n+1))\) (the base and the tuple of all the values). Let’s define a class MyNumber that can represent numbers in any base (from 1 to 9). The class has two attributes, the base (which is a number representing the base) and the values a tuple of
numbers. Let’s redefine some operators (i.e. add, lt, str) and implement some methods to covert these numbers into decimals.
[6]:
class MyNumber:
def __init__(self, base, values):
self.base = base
warn = False
for v in values:
if(v >= base):
print("Error.Values must be lower than base")
print("Can't create n. with base {} and values {}".format(base,values))
warn = True
if(not warn):
self.values = values
else:
self.values = None
def toDecimal(self):
res = 0
L = len(self.values)
for i in range(L):
res += self.values[i] * self.base**(L-1 - i)
return res
def __str__(self):
return "Base: {}\nValues:{}".format(self.base, self.values)
def __add__(self, other):
return self.toDecimal() + other.toDecimal()
def __lt__(self, other):
return self.toDecimal() < other.toDecimal()
def toDecimalString(self):
L = len(self.values)
res = str(self.values[0]) + "*" +str(self.base ** (L-1))
for i in range(1,L):
res += " + " + str(self.values[i]) + "*" + str(self.base**(L-1 - i))
return res
mn = MyNumber(10,(1,2,3))
print(mn)
print("{} = {}".format(mn.toDecimal(), mn.toDecimalString()))
mn2 = MyNumber(4, (1,2,3))
print("\n{}".format(mn2))
print("{} = {}".format(mn2.toDecimal(), mn2.toDecimalString()))
mn3 = mn + mn2
print("\nmn+mn2:{}".format(mn3))
print("\n")
mn4 = MyNumber(3,(7,1,1))
print("\n")
print("{} < {}? {}".format(mn.toDecimal(),mn2.toDecimal(),mn < mn2))
print("{} == {}? {}".format(mn.toDecimal(),mn2.toDecimal(),mn == mn2))
print("{} > {}? {}".format(mn.toDecimal(),mn2.toDecimal(),mn > mn2))
Base: 10
Values:(1, 2, 3)
123 = 1*100 + 2*10 + 3*1
Base: 4
Values:(1, 2, 3)
27 = 1*16 + 2*4 + 3*1
mn+mn2:150
Error.Values must be lower than base
Can't create n. with base 3 and values (7, 1, 1)
123 < 27? False
123 == 27? False
123 > 27? True
Inheritance and overriding
One object can inherit the attributes and methods from another object. This establishes a “Is-a” relationship between the two objects. The first object is called subclass of the original class. A subclass inherits all the methods and attributes of the superclass, but it can also redefine some methods through a process called overriding.
The syntax to define a subclass is the following:
class MySuperClass:
...
def myMethod(self,...):
...
class MySubClass(MySuperClass):
...
def myMethod(self,...):
...
basically, we just specify the superclass after the name of the subclass we are defining.
Consider the following example:
[7]:
class Person:
def __init__(self, name, surname, age):
self.name = name
self.surname = surname
self.age = age
def getInfo(self):
return "{} {} is aged {}".format(self.name,
self.surname,
self.age)
class Dad(Person):
children = []
def addChild(self,child):
self.children.append(child)
def getChildren(self):
return self.children
def getInfo(self):
personalInfo = "{} {} is aged {}".format(self.name,
self.surname,
self.age)
childrInfo = ""
for son in self.getChildren():
childrInfo += " - {}'s child is {} {}".format(
self.name, son.name, son.surname) +"\n"
return personalInfo + "\n" + childrInfo
jade = Person("Jade", "Smith",5)
print(jade.getInfo())
john = Person("John", "Smith",4)
tim = Person("Tim", "Smith",1)
dan = Dad("Dan", "Smith", 45)
dan.addChild(jade)
dan.addChild(john)
dan.addChild(tim)
print(dan.getInfo())
Jade Smith is aged 5
Dan Smith is aged 45
- Dan's child is Jade Smith
- Dan's child is John Smith
- Dan's child is Tim Smith
Note that the object Dad (subclass) is-a Person (superclass) but has a further attribute that is the list of children, each of which are of type Person. The getInfo method of the subclass Dad overrides the corresponding method of the superclass Person and prints some information on the children.
[8]:
class Person:
def __init__(self, name, surname, age):
self.name = name
self.surname = surname
self.age = age
def __str__(self):
return "{} {} is aged {}".format(self.name,
self.surname,
self.age)
class Dad(Person):
children = []
def addChild(self,child):
self.children.append(child)
def setChildren(self, children):
self.children = children
def getChildren(self):
return self.children
def __str__(self):
personalInfo = "{} {} is aged {}".format(self.name,
self.surname,
self.age)
childrInfo = ""
for son in self.getChildren():
childrInfo += " - {}'s child is {} {}".format(
self.name, son.name, son.surname) +"\n"
return personalInfo + "\n" + childrInfo
jade = Person("Jade", "Smith",5)
#print(jade.getInfo())
john = Person("John", "Smith",4)
tim = Person("Tim", "Smith",1)
dan = Dad("Dan", "Smith", 45)
dan.setChildren([jade,john])
#dan.addChild(john)
dan.addChild(tim)
print(jade)
print(dan)
Jade Smith is aged 5
Dan Smith is aged 45
- Dan's child is Jade Smith
- Dan's child is John Smith
- Dan's child is Tim Smith
Exercise
Implement a superclass Polygon with two attributes:
shape
sides’ length.
Add a method to return the perimeter and a method to return the number of edges of the polygon. Implement a __str__ method to print an object of the class with its shape. Then create two subclasses of poylgon: Rectangle and Triangle. For each subclass modify the initializer accordingly and also override the __str__ to print the object id.
Show/Hide solution
[9]:
class Polygon:
def __init__(self):
self.shape = 'polygon'
self.side_len = None
def get_perimeter(self):
return sum(self.side_len)
def get_number_of_edges(self):
return len(self.side_len)
def __str__(self):
return "I am a {}".format(self.shape)
class Rectangle(Polygon):
def __init__(self):
self.shape = 'rectangle'
self.side_len = [2, 2,2, 2]
def __str__(self):
return super().__str__() + " with id: {}. ".format(id(self))
class Triangle(Polygon):
def __init__(self):
self.shape = 'triangle'
self.side_len = [4, 4, 4]
def __str__(self):
return super().__str__() + " with id: {}. ".format(id(self))
p = Polygon()
print(p)
rect = Rectangle()
print(rect)
print(rect.get_number_of_edges())
print(rect.get_perimeter())
tr = Triangle()
print(tr)
print(tr.get_number_of_edges())
print(tr.get_perimeter())
I am a polygon
I am a rectangle with id: 140436237036112.
4
8
I am a triangle with id: 140436237125072.
3
12
Lambda functions
Computing in the functional programming paradigm is obtained by applying functions to a set of inputs. To have an idea about the differences with OOP, see this link: https://dev.to/documatic/when-to-use-functional-programming-vs-oop-122n
Three mechanisms are available in python:
map :
map(f, input_list)applies the functionfto all the elements ofinput_list;filter :
filter(f, input_list)filtersinput_listbased on a functionfthat returns true or false for each of the input elements;reduce :
reduce(f, input_list)applies the functionfto the first two elements of the input list, then it applies it to the result and to the third element and so on until the end of the list is reached and one value only is returned.
Note that the reduce function is part of the functools module and needs to be imported with:
from functools import reduce
Let’s see some examples of map and filter.
Example: Let’s define two functions to convert temperatures from fahrenheit to celsius and vice-versa. Let’s finally use them to work out the temperatures that are above and below freezing.
[10]:
def fahrenheit(T):
return ((float(9)/5)*T + 32)
def celsius(T):
return (float(5)/9)*(T-32)
def freezingFiltF(T):
return celsius(T) < 0
def freezingFiltC(T):
return T < 0
farTmp = (27.5, 29, 37.5,12, 44, 72,100)
C = map(celsius, farTmp)
print(type(C))
C = tuple(C)
for i in range(len(C)):
print("{:.2f}°F --> {:.2f}°C".format(farTmp[i],C[i]))
print("\n")
print("Freezing temperatures:")
print(type(filter(freezingFiltF, farTmp)))
ftF = tuple(filter(freezingFiltF, farTmp))
ftC = tuple(filter(freezingFiltC, C))
for i in range(len(ftF)):
print("{:.2f}°F --> {:.2f}°C".format(ftF[i],ftC[i]))
<class 'map'>
27.50°F --> -2.50°C
29.00°F --> -1.67°C
37.50°F --> 3.06°C
12.00°F --> -11.11°C
44.00°F --> 6.67°C
72.00°F --> 22.22°C
100.00°F --> 37.78°C
Freezing temperatures:
<class 'filter'>
27.50°F --> -2.50°C
29.00°F --> -1.67°C
12.00°F --> -11.11°C
Note that map and filter respectively return an object map and an object filter which we need to convert into lists or tuples if we want to work with the results.
Examples
Use reduce to:
sum the elements of a list
count how many non-space characters a string has
[11]:
from functools import reduce
lst = [4,5,6,7]
reducedLst = reduce(int.__add__, lst)
print(reducedLst)
22
[12]:
from functools import reduce
myText = "Testing shows the presence, not the absence of bugs"
words = myText.split()
print("Word sizes: {}".format(list(map(len, words))))
cnt = reduce(int.__add__, list(map(len, words)))
print("Dijkstra's quote has {} characters".format(cnt))
Word sizes: [7, 5, 3, 9, 3, 3, 7, 2, 4]
Dijkstra's quote has 43 characters
All the above examples required the specification of a function. We do not necessarily need to specify a function name in Python thanks to the anonimous functions also known as lambda functions.
Their basic syntax is:
lambda input-parameters: expression
where the keyword lambda proceeds the comma separated list of input parameters and a colon : marks the beginning of the expression. We can anyway give a name to the lambda function with:
myfunct = lambda input-parameters: expression
Some examples of lambda functions follow:
[13]:
sum_lambda = lambda x, y : x+y
mult_lambda = lambda x,y : x*y
cap_lambda = lambda x : x.capitalize()
print(sum_lambda(10,20))
print(sum_lambda("Hi ", "there!"))
print("\n")
print(mult_lambda(10,20))
print(mult_lambda("Hi! ", 3))
print("\n")
txt = "hi there from luca!"
print(cap_lambda(txt))
print(" ".join(map(cap_lambda, txt.split())))
30
Hi there!
200
Hi! Hi! Hi!
Hi there from luca!
Hi There From Luca!
More interesting uses of lambda involve sorting of tuples through the key parameter:
Example: Let’s count how many occurrences we have for each non-space character in the string “Testing shows the presence, not the absence of bugs”. And print the least and most frequent character, as well as the first and last in alphabetic order.
[14]:
myText = "testing shows the presence, not the absence of bugs"
cnts = [(x,myText.count(x)) for x in myText if x != " " and x != ","]
uniqueCnts = []
for e in cnts:
if(e not in uniqueCnts):
uniqueCnts.append(e)
print(uniqueCnts)
get_first = lambda x : x[0]
get_second = lambda x : x[1]
#Get the most and least frequent character(s).
uniqueCnts.sort(key = get_second)
all_least_f = list(filter(lambda x: x[1] == uniqueCnts[0][1], uniqueCnts))
all_most_f = list(filter(lambda x: x[1] == uniqueCnts[-1][1], uniqueCnts))
print("All least frequent: {}".format(all_least_f))
print("All most frequent: {}".format(all_most_f))
#Get the most and least frequent character.
uniqueCnts.sort(key = get_first)
print("The first in alphabetic order: {}".format(uniqueCnts[0]))
print("The last in alphabetic order: {}".format(uniqueCnts[-1]))
[('t', 5), ('e', 8), ('s', 6), ('i', 1), ('n', 4), ('g', 2), ('h', 3), ('o', 3), ('w', 1), ('p', 1), ('r', 1), ('c', 2), ('a', 1), ('b', 2), ('f', 1), ('u', 1)]
All least frequent: [('i', 1), ('w', 1), ('p', 1), ('r', 1), ('a', 1), ('f', 1), ('u', 1)]
All most frequent: [('e', 8)]
The first in alphabetic order: ('a', 1)
The last in alphabetic order: ('w', 1)
Exercises
Create a Consumer class with:
wealth as attribute (amount of money)
earn method, where earn(m) increments the consumer’s wealth by m
a spend method, where spend(m) either decreases wealth by m or returns an error if there are not enough money
a
__str__will print the wealth at the current time
Show/Hide solution
[15]:
class Consumer:
def __init__(self, w):
self.wealth = w
def earn(self, m):
self.wealth += m
def spend(self, m):
new_wealth = self.wealth - m
if new_wealth < 0:
print("Not enough money!")
else:
self.wealth = new_wealth
def __str__(self):
return "Current wealth of consumer: {}.".format(self.wealth)
c1 = Consumer(100) # Create instance with initial wealth 100
print(c1)
c1.spend(23)
print(c1)
c1.earn(100)
print(c1)
c2= Consumer(20)
c2.spend(100)
Current wealth of consumer: 100.
Current wealth of consumer: 77.
Current wealth of consumer: 177.
Not enough money!
Define a 3D point class (
Point3D) which contains three attributes that are the (x,y,z) coordinates in the 3D space and a string (label). Implement acomputeDistancemethod that computes the distance between the point and another point. Remember that if \(a = (x_a,y_a,z_a)\) and \(b = (x_b,y_b,z_b)\), \(distance(a,b) = \sqrt{{(x_a-x_b)}^2 + {(y_a-y_b)}^2 + {(z_a-z_b)}^2}\).
Given the following points:
p = Point3D(0,10,0, "alfa")
p1 = Point3D(10,20,10, "point")
p2 = Point3D(4,9, 10, "other")
p3 = Point3D(8,9,11, "zebra")
p4 = Point3D(0,10,10, "label")
p5 = Point3D(0,10,10, "last")
p6 = Point3D(42,102,10, "fifth")
Write some code to find out the closest pair of 3D points;
Sort the points by distance to the origin (0,0,0) and print them in this order;
Sort the points alphabetically by label (and print them).
Show/Hide solution
[16]:
#from previous edition of the class
import math
class Point3D:
def __init__(self,x,y,z, lab):
self.x = x
self.y = y
self.z = z
self.label = lab
def computeDistance(self, otherP):
dx = (self.x - otherP.x)**2
dy = (self.y - otherP.y)**2
dz = (self.z - otherP.z)**2
return math.sqrt(dx + dy + dz)
def __str__(self):
return "{}:({},{},{})".format(self.label, self.x,self.y,self.z)
p = Point3D(0,10,0, "alfa")
p1 = Point3D(10,20,10, "point")
p2 = Point3D(4,9, 10, "other")
p3 = Point3D(8,9,11, "zebra")
p4 = Point3D(0,10,10, "label")
p5 = Point3D(0,10,10, "last")
p6 = Point3D(42,102,10, "fifth")
allPoints = [p, p1, p2, p3, p4,p5, p6]
minDist = 10000000
closest = None
for i in range(len(allPoints)):
for j in range(i+1, len(allPoints)):
d = allPoints[i].computeDistance(allPoints[j])
if(d < minDist):
closest = (allPoints[i],allPoints[j])
minDist = d
print("Closest pair (d:{}): ({}, {})".format(minDist,closest[0],closest[1]))
#sort the points by distance from origin.
lambda_dist = lambda x : x.computeDistance(Point3D(0,0,0,"origin"))
allPoints.sort(key = lambda_dist)
for el in allPoints:
print("{} distance from origin:{:.2f}".format(el,lambda_dist(el)))
#sort by label
lambda_label = lambda x : x.label
allPoints.sort(key = lambda_label)
print("\nSort by label")
for el in allPoints:
print("{} distance from origin:{:.2f}".format(el,lambda_dist(el)))
Closest pair (d:0.0): (label:(0,10,10), last:(0,10,10))
alfa:(0,10,0) distance from origin:10.00
other:(4,9,10) distance from origin:14.04
label:(0,10,10) distance from origin:14.14
last:(0,10,10) distance from origin:14.14
zebra:(8,9,11) distance from origin:16.31
point:(10,20,10) distance from origin:24.49
fifth:(42,102,10) distance from origin:110.76
Sort by label
alfa:(0,10,0) distance from origin:10.00
fifth:(42,102,10) distance from origin:110.76
label:(0,10,10) distance from origin:14.14
last:(0,10,10) distance from origin:14.14
other:(4,9,10) distance from origin:14.04
point:(10,20,10) distance from origin:24.49
zebra:(8,9,11) distance from origin:16.31
Write a Person class with the following attributes: name, surname and mailbox. A mailbox is a list that contains string messages sent to the person. Each message entry should be a tuple
(name, surname, message)wherenameandsurnameare the name and surname of the sender of the message, whilemessageis a string with the text of the message. Implement the following methods:getName: gets the name of the Person;getSurname: gets the surname of the Person;sendMessage: that has aPersonand the string with the message in input and sends the message to the specified Person;checkMailbox: returns the number of messages in the mailbox;readMessages: returns all the messages in the mailbox as a list;checkMessagesFrom: checks and prints any messages coming from a specific Person (in input);clearMailbox: clears the mailbox (does not return anything);
Test the class with the following conversation:
luca = Person("Luca", "Bianco")
alberto = Person("Alberto", "Montresor")
david = Person("David", "Leoni")
stranger = ""
luca.sendMessage(alberto, "Hi Alberto, hope things are fine.")
alberto.sendMessage(luca, "I am fine, thanks. Yourself?")
luca.sendMessage(alberto, "Great. Cheers. How about David?")
alberto.sendMessage(david, "You OK?")
david.sendMessage(alberto, "Yep. Thanks")
alberto.sendMessage(luca, "All OK")
luca.sendMessage(stranger, "Who are you?")
and check the mailbox of all the Persons in [luca,alberto,david].
Show/Hide solution
[17]:
class Person:
def __init__(self, name, surname):
self.__n = name
self.__s = surname
self.mailbox = []
def getName(self):
return self.__n
def getSurname(self):
return self.__s
def sendMessage(self, otherPerson, msg):
if(type(otherPerson) == Person):
otherPerson.mailbox.append((self.__n, self.__s, msg))
else:
print("Unable to send message. Person not found")
def checkMailbox(self):
if(len(self.mailbox) == 0 ):
return 0
else:
return len(self.mailbox)
def readMessages(self):
return self.mailbox
def checkMessagesFrom(self, otherPerson):
if(type(otherPerson) == Person):
msgs = [x for x in self.mailbox
if x[0] == otherPerson.getName()
and x[1] == otherPerson.getSurname()
]
return msgs
else:
return []
def clearMailbox(self):
self.mailbox.clear()
def __str__(self):
return str(self.__n) + " " + str(self.__s)
luca = Person("Luca", "Bianco")
alberto = Person("Alberto", "Montresor")
david = Person("David", "Leoni")
stranger = ""
luca.sendMessage(alberto, "Hi Alberto, hope things are fine.")
alberto.sendMessage(luca, "I am fine, thanks. Yourself?")
luca.sendMessage(alberto, "Great. Cheers. How about David?")
alberto.sendMessage(david, "You OK?")
david.sendMessage(alberto, "Yep. Thanks")
alberto.sendMessage(luca, "All OK")
luca.sendMessage(stranger, "Who are you?")
for p in [luca, alberto,david]:
print("\n{} has {} new message(s):".format(p,p.checkMailbox()))
for m in p.readMessages():
print(" - from {} {}: {}".format(m[0],
m[1],
m[2]))
for p in [luca, alberto, david]:
for p1 in [luca, alberto, david]:
if(p != p1):
msgs = p.checkMessagesFrom(p1)
if(len(msgs) > 0):
print("\n")
for m in msgs:
print("{} --> {}: {}".format(p1,p,m[2]))
else:
print("\n{} does not have messages from {}".format(p, p1))
luca.clearMailbox()
print("\n{} has {} new message(s).".format(luca,luca.checkMailbox()))
Unable to send message. Person not found
Luca Bianco has 2 new message(s):
- from Alberto Montresor: I am fine, thanks. Yourself?
- from Alberto Montresor: All OK
Alberto Montresor has 3 new message(s):
- from Luca Bianco: Hi Alberto, hope things are fine.
- from Luca Bianco: Great. Cheers. How about David?
- from David Leoni: Yep. Thanks
David Leoni has 1 new message(s):
- from Alberto Montresor: You OK?
Alberto Montresor --> Luca Bianco: I am fine, thanks. Yourself?
Alberto Montresor --> Luca Bianco: All OK
Luca Bianco does not have messages from David Leoni
Luca Bianco --> Alberto Montresor: Hi Alberto, hope things are fine.
Luca Bianco --> Alberto Montresor: Great. Cheers. How about David?
David Leoni --> Alberto Montresor: Yep. Thanks
David Leoni does not have messages from Luca Bianco
Alberto Montresor --> David Leoni: You OK?
Luca Bianco has 0 new message(s).