Skip to content

Εργαστήριο 4 στην Python

Θέματα που εξετάζονται στο εργαστήριο: αντικειμενοστραφής προγραμματισμός με την Python1, οι μέθοδοι __init__, __str__, __repr__, υπερφόρτωση τελεστών2, κληρονομικότητα, εξαιρέσεις3, ορίσματα γραμμής εντολών με το sys.argv και με το argparse, logging.

Εξάσκηση (εκφωνήσεις και λύσεις ασκήσεων)

Άσκηση E4A1 - Δημιουργήστε μια κλάση Car με μέλη δεδομένων τη μάρκα (brand), το μοντέλο (model) και το έτος κατασκευής (year). Υλοποιήστε τη μέθοδο κατασκευής __init__ και τη μέθοδο __str__ που θα εμφανίζει πληροφορίες για κάθε αντικείμενο τύπου Car. Διαβάστε τα ακόλουθα δεδομένα και δημιουργήστε στιγμιότυπα αντικειμένων Car. Ταξινομήστε τα αντικείμενα πρώτα με βάση το έτος κατασκευής, και μετά με βάση τη μάρκα. Εμφανίστε τα ταξινομημένα αντικείμενα.

cars = """
#year,brand,model
1969,Dodge,Charger
1963,Corvette, Stingray
1974,Porsche,914
1969,Chevrolet,Camaro Z28
1967,Toyota,2000GT
1971,Ford,Thunderbird
1991,Dodge,Viper
1966,Lamborghini,Miura
1962,Ferrari,250 GTO
1954,Mercedes,300SL Gullwing"""
Λύση άσκησης E4A1

4_1_sol.py
class Car:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year

    def __str__(self):
        return f"Brand: {self.brand}, model: {self.model}, year: {self.year}"


cars = """
#year,brand,model
1969,Dodge,Charger
1963,Corvette, Stingray
1974,Porsche,914
1969,Chevrolet,Camaro Z28
1967,Toyota,2000GT
1971,Ford,Thunderbird
1991,Dodge,Viper
1966,Lamborghini,Miura
1962,Ferrari,250 GTO
1954,Mercedes,300SL Gullwing
"""

if __name__ == "__main__":
    cars_list = []
    for line in cars.split("\n"):
        line = line.strip()
        if line == "" or line[0] == "#":
            continue
        year, brand, model = line.split(",")
        car = Car(brand, model, int(year))
        cars_list.append(car)

    cars_list = sorted(cars_list, key=lambda car: car.brand)  # ταξινόμηση κατά μάρκα
    cars_list = sorted(
        cars_list, key=lambda car: car.year
    )  # πρώτα ταξινόμηση κατά έτος
    for car in cars_list:
        print(car)
Παράδειγμα εκτέλεσης:
$ python 4_1_sol.py
Brand: Mercedes, model: 300SL Gullwing, year: 1954
Brand: Ferrari, model: 250 GTO, year: 1962
Brand: Corvette, model:  Stingray, year: 1963
Brand: Lamborghini, model: Miura, year: 1966
Brand: Toyota, model: 2000GT, year: 1967
Brand: Chevrolet, model: Camaro Z28, year: 1969
Brand: Dodge, model: Charger, year: 1969
Brand: Ford, model: Thunderbird, year: 1971
Brand: Porsche, model: 914, year: 1974
Brand: Dodge, model: Viper, year: 1991

Άσκηση E4A2 - Δίνεται ο ακόλουθος κώδικας που ορίζει την κλάση Juice και υπερφορτώνει τον τελεστή +.

class Juice:
    def __init__(self, name, capacity):
        self.name = name
        self.capacity = capacity

    def __str__(self):
        return (self.name + ' ('+str(self.capacity)+'L)')

    def __add__(self, other):
        return Juice(self.name + '&' + other.name, self.capacity + other.capacity)

a = Juice('Orange', 1.5)
b = Juice('Apple', 2.0)
result = a + b
print(result)
  • Προσθέστε το επιπλέον πεδίο τιμή (price).
  • Όταν εφαρμόζεται ο τελεστής πρόσθεσης ο νέος χυμός να έχει τιμή ίση με το άθροισμα τιμών των δύο χυμών.
  • Προσθέστε τη μέθοδο pour που να δέχεται ως παράμετρο μια τιμή από το 0% μέχρι το 100% και να επιστρέφει ένα νέο αντικείμενο Juice με το χυμό που προκύπτει αν ληφθεί το αντίστοιχο ποσοστό περιεχομένου από το καλών αντικείμενο.
Λύση άσκησης E4A2

4_2_sol.py
class Juice:
    def __init__(self, name, capacity, price):
        self.name = name
        self.capacity = capacity
        self.price = price

    def __str__(self):
        return f"{self.name} ({self.capacity:.2f}L) {self.price:.2f} €"

    def __add__(self, other):
        new_price = self.price + other.price
        return Juice(
            self.name + "&" + other.name, self.capacity + other.capacity, new_price
        )

    def pour(self, percentage):
        assert 0.0 < percentage <= 1.0
        print(f"Pouring {percentage*100}% from {str(self)}")
        new_capacity = self.capacity * percentage
        new_price = self.price * percentage
        self.capacity -= new_capacity
        self.price -=  new_price
        return Juice(self.name, new_capacity, new_price)


a = Juice("Orange", 1.5, 3.5)
b = Juice("Apple", 2.0, 3.0)
print(a)
print(b)

result = a + b
print(result)

c = result.pour(0.4)
print(result)
print(c)
Παράδειγμα εκτέλεσης:
Orange (1.50L) 3.50 €
Apple (2.00L) 3.00 €
Orange&Apple (3.50L) 6.50 €
Pouring 40.0% from Orange&Apple (3.50L) 6.50 €
Orange&Apple (2.10L) 3.90 €
Orange&Apple (1.40L) 2.60 €

Άσκηση E4A3 - Δημιουργήστε μια κλάση Stack που να ορίζει μια στοίβα. Η στοίβα να υποστηρίζει τις λειτουργίες ώθηση (push), απώθηση (pop), εκκαθάριση περιεχομένων στοίβας (clear) και εμφάνιση περιεχομένων στοίβας (με τη χρήση του __str__). Τροποποιήστε το ακόλουθο template κώδικα έτσι ώστε η λύση σας να επιτυγχάνει σε όλα τα unit tests.

template4_3.py
import unittest


class Stack:
    def __init__(self):
        self.data = []

    def push(self, item):
        pass

    def pop(self):
        pass

    def clear(self):
        pass

    def __str__(self):
        pass

# Mην αλλάξετε κάτι από εδώ και κάτω
class TestHammingDistance(unittest.TestCase):
    def test_Stack(self):
        s = Stack()
        self.assertTrue(str(s)=="Η στοίβα δεν περιέχει στοιχεία")
        s.push(1)
        self.assertTrue(str(s)=="->1\n")
        s.push(2)
        self.assertTrue(str(s)=="->2\n  1\n")
        s.push(3)
        self.assertTrue(str(s)=="->3\n  2\n  1\n")
        s.pop()
        self.assertTrue(str(s)=="->2\n  1\n")

if __name__ == "__main__":
    unittest.main()
Λύση άσκησης E4A3

template4_3_sol.py
import unittest


class Stack:
    EMPTY_MSG = "Η στοίβα δεν περιέχει στοιχεία"

    def __init__(self):
        self.data = []

    def push(self, item):
        self.data.append(item)

    def pop(self):
        if len(self.data) > 0:
            self.data.pop()
        else:
            print(Stack.EMPTY_MSG)

    def clear(self):
        self.data = []

    def __str__(self):
        if len(self.data) == 0:
            return Stack.EMPTY_MSG
        else:
            s = ""
            for idx, item in enumerate(reversed(self.data)):
                if idx == 0:
                    s += f"->{item}\n"
                else:
                    s += f"  {item}\n"
            return s


# Mην αλλάξετε κάτι από εδώ και κάτω
class TestHammingDistance(unittest.TestCase):
    def test_Stack(self):
        s = Stack()
        self.assertTrue(str(s)=="Η στοίβα δεν περιέχει στοιχεία")
        s.push(1)
        self.assertTrue(str(s)=="->1\n")
        s.push(2)
        self.assertTrue(str(s)=="->2\n  1\n")
        s.push(3)
        self.assertTrue(str(s)=="->3\n  2\n  1\n")
        s.pop()
        self.assertTrue(str(s)=="->2\n  1\n")

if __name__ == "__main__":
    unittest.main()
Παράδειγμα εκτέλεσης:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Άσκηση E4A4 - Κατασκευάστε την κλάση Document που να αναπαριστά ένα έγγραφο που να περιέχει τη λίστα συντακτών του (authors) και την ημερομηνία δημιουργίας του (creation_date). Η κλάση Document να διαθέτει τη μέθοδο add_author για την προσθήκη συντακτών στο έγγραφο. Από την κλάση Document κληρονομήστε σε δύο άλλες κλάσεις, την υποκλάση Book και την υποκλάση Email. Για την υποκλάση Book ορίστε το επιπλέον πεδίο title (τίτλος βιβλίου). Για την υποκλάση Email ορίστε τα επιπλέον πεδία sender (αποστολέας), subject (θέμα), recipients (λίστα παραληπτών). Η υποκλάση Recipient να διαθέτει τη μέθοδο add_recipient για την προσθήκη παραληπτών. Για όλες τις κλάσεις ορίστε κατάλληλους κατασκευαστές και τη συνάρτηση __str__. Τροποποιήστε το ακόλουθο template κώδικα:

template4_4.py
import datetime


class Document:
    def __init__(self, creation_date):
        pass

    def add_author(self, name):
        pass

    def __str__(self):
        pass


class Book(Document):
    def __init__(self, creation_date, title):
        pass

    def __str__(self):
        pass


class Email(Document):
    def __init__(self, creation_date, sender, subject):
        pass

    def add_recipient(self, name):
        pass

    def __str__(self):
        pass


if __name__ == "__main__":
    documents = []
    d1 = Document(datetime.datetime(2022, 3, 24, 9, 30))
    d1.add_author("Nikos")
    d2 = Document(datetime.datetime(2022, 3, 24, 10, 20))
    d2.add_author("Petros")
    d2.add_author("Maria")

    d3 = Book(datetime.datetime(2021, 1, 1, 0, 0), "Philosophy 101")
    d3.add_author("Socrates")
    d3.add_author("Descartes")
    d3.add_author("Nietschie")

    d4 = Email(datetime.datetime(2022, 3, 26, 10, 30), "Panayiotis", "Important notice")
    d4.add_author("Panayiotis")
    d4.add_recipient("Maria")

    d5 = Email(datetime.datetime(2022, 3, 21, 22, 45), "Marianthi", "SPAM")
    d5.add_author("Marianthi")
    d5.add_author("Vasilis")
    d5.add_recipient("Maria")
    d5.add_recipient("Christos")
    d5.add_recipient("Vasilis")
    d5.add_recipient("Sofia")

    documents.append(d1)
    documents.append(d2)
    documents.append(d3)
    documents.append(d4)
    documents.append(d5)

    for d in documents:
        print(d)

έτσι ώστε η έξοδος να είναι η ακόλουθη:

Document created at 2022-03-24 09:30:00 authors=Nikos
Document created at 2022-03-24 10:20:00 authors=Petros, Maria
Document created at 2021-01-01 00:00:00 authors=Socrates, Descartes, Nietschie title=Philosophy 101 type=BOOK
Document created at 2022-03-26 10:30:00 authors=Panayiotis sender=Panayiotis subject=Important notice recipients=Maria type=EMAIL
Document created at 2022-03-21 22:45:00 authors=Marianthi, Vasilis sender=Marianthi subject=SPAM recipients=Maria, Christos, Vasilis, Sofia type=EMAIL
  • Συμπληρώστε τον κώδικα στη main έτσι ώστε η λίστα εγγράφων να εμφανίζεται ταξινομημένη σε αύξουσα σειρά ημερομηνίας και ώρας δημιουργίας.
Λύση άσκησης E4A4

template4_4_sol.py
import datetime


class Document:
    def __init__(self, creation_date):
        self.authors = []
        self.creation_date = creation_date

    def add_author(self, name):
        self.authors.append(name)

    def __str__(self):
        return f"Document created at {self.creation_date} authors={', '.join(self.authors)}"


class Book(Document):
    def __init__(self, creation_date, title):
        Document.__init__(self, creation_date)
        self.title = title

    def __str__(self):
        return f"{Document.__str__(self)} title={self.title} type=BOOK"


class Email(Document):
    def __init__(self, creation_date, sender, subject):
        Document.__init__(self, creation_date)
        self.sender = sender
        self.subject = subject
        self.recipients = []

    def add_recipient(self, name):
        self.recipients.append(name)

    def __str__(self):
        return f"{Document.__str__(self)} sender={self.sender} subject={self.subject} recipients={', '.join(self.recipients)} type=EMAIL"


if __name__ == "__main__":
    documents = []
    d1 = Document(datetime.datetime(2022, 3, 24, 9, 30))
    d1.add_author("Nikos")
    d2 = Document(datetime.datetime(2022, 3, 24, 10, 20))
    d2.add_author("Petros")
    d2.add_author("Maria")

    d3 = Book(datetime.datetime(2021, 1, 1, 0, 0), "Philosophy 101")
    d3.add_author("Socrates")
    d3.add_author("Descartes")
    d3.add_author("Nietschie")

    d4 = Email(datetime.datetime(2022, 3, 26, 10, 30), "Panayiotis", "Important notice")
    d4.add_author("Panayiotis")
    d4.add_recipient("Maria")

    d5 = Email(datetime.datetime(2022, 3, 21, 22, 45), "Marianthi", "SPAM")
    d5.add_author("Marianthi")
    d5.add_author("Vasilis")
    d5.add_recipient("Maria")
    d5.add_recipient("Christos")
    d5.add_recipient("Vasilis")
    d5.add_recipient("Sofia")

    documents.append(d1)
    documents.append(d2)
    documents.append(d3)
    documents.append(d4)
    documents.append(d5)

    for d in documents:
        print(d)

    # Εμφάνιση λίστας εγγράφων ταξινομημένων κατά ημερομηνία δημιουργίας
    print("#" * 80)
    for d in sorted(documents, key=lambda x: x.creation_date):
        print(d)
Παράδειγμα εκτέλεσης:
Document created at 2022-03-24 09:30:00 authors=Nikos
Document created at 2022-03-24 10:20:00 authors=Petros, Maria
Document created at 2021-01-01 00:00:00 authors=Socrates, Descartes, Nietschie title=Philosophy 101 type=BOOK
Document created at 2022-03-26 10:30:00 authors=Panayiotis sender=Panayiotis subject=Important notice recipients=Maria type=EMAIL
Document created at 2022-03-21 22:45:00 authors=Marianthi, Vasilis sender=Marianthi subject=SPAM recipients=Maria, Christos, Vasilis, Sofia type=EMAIL
################################################################################
Document created at 2021-01-01 00:00:00 authors=Socrates, Descartes, Nietschie title=Philosophy 101 type=BOOK
Document created at 2022-03-21 22:45:00 authors=Marianthi, Vasilis sender=Marianthi subject=SPAM recipients=Maria, Christos, Vasilis, Sofia type=EMAIL
Document created at 2022-03-24 09:30:00 authors=Nikos
Document created at 2022-03-24 10:20:00 authors=Petros, Maria
Document created at 2022-03-26 10:30:00 authors=Panayiotis sender=Panayiotis subject=Important notice recipients=Maria type=EMAIL

Επιπλέον εξάσκηση

Άσκηση E4A5 - Δημιουργήστε μια κλάση με όνομα Cylinder (κύλινδρος) που:

  1. Nα έχει ως «ιδιωτικά μέλη» δεδομένων τα πεδία _height (ύψος κυλίνδρου) και _radius (ακτίνα βάσης κυλίνδρου).
  2. Ορίστε με την __init__ κατασκευαστή που να θέτει τιμές και για τα δύο της μέλη δεδομένων μέσω παραμέτρων που θα δέχεται.
  3. Ορίστε μια συνάρτηση με όνομα volume που να επιστρέφει τον όγκο του κυλίνδρου με βάση τον τύπο \(V=\pi r^2h\), όπου \(r\) είναι η ακτίνα της βάσης και \(h\) είναι το ύψος του κυλίνδρου (και \(\pi = 3.14\)).
  4. Συμπληρώστε τη συνάρτηση __str__ έτσι ώστε να εμφανίζει τα στοιχεία ενός κυλίνδρου στη μορφή (ακτίνα, ύψος, όγκος).
  5. Στη __main__ γράψτε κώδικα που να δημιουργεί μια λίστα με αντικείμενα Cylinder και προσθέστε 5 αντικείμενα Cylinder με τιμές ύψους κυλίνδρου και ακτίνας βάσης που θα δίνει ο χρήστης. Ταξινομήστε με τη συνάρτηση sort τα περιεχόμενα της λίστας σε φθίνουσα σειρά βάσει όγκου και εμφανίστε προκαλώντας την κλήση της συνάρτησης __str__ για καθένα από τα στοιχεία της λίστας.
Λύση άσκησης E4A5
e4a5.py
import math


class Cylinder:
    def __init__(self, radius, height):
        self._radius = radius
        self._height = height

    def __str__(self):
        return (
            f"(ακτίνα={self._radius}, ύψος={self._height}, όγκος={self.volume():.2f})"
        )

    def volume(self):
        return math.pi * self._radius**2 * self._height


if __name__ == "__main__":
    list_of_cylinders = []
    for i in range(5):
        while True:
            try:
                r, h = input(f"Δώσε ακτίνα και ύψος για κύλιδρο {i+1}: ").split()
                r = float(r)
                h = float(h)
                if r > 0 and h > 0:
                    break
                else:
                    print("Λάθος τιμή, εισάγετε θετικές τιμές")
            except ValueError:
                print("Λάθος τιμή, εισάγετε αριθμητικές τιμές με υποδιαστολή την .")
        list_of_cylinders.append(Cylinder(r, h))

    list_of_cylinders.sort(reverse=True, key=lambda c: c.volume())

    for cyl in list_of_cylinders:
        print(cyl)

Παράδειγμα εκτέλεσης:

Δώσε ακτίνα και ύψος για κύλιδρο 1: 2 3.4
Δώσε ακτίνα και ύψος για κύλιδρο 2: 1.2 4.5
Δώσε ακτίνα και ύψος για κύλιδρο 3: 12.5 8.9
Δώσε ακτίνα και ύψος για κύλιδρο 4: 3.3 6.7
Δώσε ακτίνα και ύψος για κύλιδρο 5: 12.1 90.1
(ακτίνα=12.1, ύψος=90.1, όγκος=41442.45)
(ακτίνα=12.5, ύψος=8.9, όγκος=4368.78)
(ακτίνα=3.3, ύψος=6.7, όγκος=229.22)
(ακτίνα=2.0, ύψος=3.4, όγκος=42.73)
(ακτίνα=1.2, ύψος=4.5, όγκος=20.36)

Άσκηση E4A6 - Ορίστε μια κλάση με όνομα Interval (διάστημα) που:

  1. Να έχει ως «ιδιωτικά» μέλη δεδομένων τα πεδία _from (από) και _to (έως) που θα αναπαριστούν ένα διάστημα, π.χ. το διάστημα [3, 7) που έχει μήκος 4.
  2. Να έχει έναν κατασκευαστή που να θέτει τιμές και για τα δύο ιδιωτικά της μέλη, μέσω παραμέτρων που θα δέχεται. Ωστόσο, αν γίνει απόπειρα να εισαχθεί τιμή στο _from μεγαλύτερη ή ίση του _to, τότε να προκαλείται μια εξαίρεση (exception) τύπου ValueError.
  3. Να έχει μια μέθοδο με όνομα length που να επιστρέφει το μήκος του διαστήματος. Ορίστε το length ως property έτσι ώστε αν εκχωρηθεί τιμή στο length τότε να προσαρμόζεται η τιμή του πεδίου _to.
  4. Προσθέστε τις magic μεθόδους __str__ και __repr__.
  5. Να έχει μια μεταβλητή κλάσης με όνομα _cnt που να μετρά τα αντικείμενα Interval που έχουν δημιουργηθεί με έγκυρα στοιχεία για τα πεδία _from και _to. Να επιστρέφεται η τιμή της _cnt μέσω μιας στατικής μεθόδου της κλάσης με όνομα number_of_intervals.
  6. Στη __main__ γράψτε κώδικα που να ζητά από τον χρήστη να εισάγει 5 δεδομένα διαστημάτων και αν προκαλείται εξαίρεση, η εξαίρεση να «συλλαμβάνεται» και να εμφανίζεται μήνυμα, αλλιώς να εμφανίζεται το μήκος του διαστήματος (με την __str__), να αλλάζει σε 10 και να εμφανίζεται εκ νέου το διάστημα με την __repr__. Με την ολοκλήρωση της εισαγωγής των δεδομένων να εμφανίζεται το πλήθος των αντικειμένων Interval.
Λύση άσκησης E4A6

e4a6.py
class Interval:
    _cnt = 0

    @staticmethod
    def number_of_intervals():
        return Interval._cnt

    def __init__(self, f, t):
        if f >= t:
            raise ValueError('To "από" πρέπει να είναι μικρότερο του "μέχρι"')
        self._from = f
        self._to = t
        Interval._cnt += 1

    @property
    def length(self):
        return self._to - self._from

    @length.setter
    def length(self, new_length):
        self._to = self._from + new_length

    def __str__(self):
        return f"[{self._from},{self._to})"

    def __repr__(self):
        return f"{self._from=} {self._to=}"


def main():
    for _ in range(5):
        try:
            f, t = input('Δώσε "από", "μέχρι" όρια διαστήματος: ').split()
            f = int(f)
            t = int(t)
            an_interval = Interval(f, t)
            print(an_interval)
            an_interval.length = 10
            print(repr(an_interval))
        except ValueError as ve:
            print(ve)
    print(
        f"Το πλήθος έγκυρων διαστημάτων που εισήχθησαν είναι {Interval.number_of_intervals()}"
    )


if __name__ == "__main__":
    main()
Παράδειγμα εκτέλεσης:
Δώσε "από", "μέχρι" όρια διαστήματος: 1 5
[1,5)
self._from=1 self._to=11
Δώσε "από", "μέχρι" όρια διαστήματος: 4 6
[4,6)
self._from=4 self._to=14
Δώσε "από", "μέχρι" όρια διαστήματος: 4 2
To "από" πρέπει να είναι μικρότερο του "μέχρι"
Δώσε "από", "μέχρι" όρια διαστήματος: 5 1
To "από" πρέπει να είναι μικρότερο του "μέχρι"
Δώσε "από", "μέχρι" όρια διαστήματος: 1 100
[1,100)
self._from=1 self._to=11
Το πλήθος έγκυρων διαστημάτων που εισήχθησαν είναι 3

Άσκηση E4A74 - Ορίστε την κλάση Length έτσι ώστε να δημιουργούνται αντικείμενα με εντολές της μορφής:

Length(5.5, "cm")
Length(5.5, "in")

Ορίστε τις μεθόδους __str__ και __repr__ της Length. Υπερφορτώστε τον τελεστή + έτσι ώστε να αθροίζονται δύο αντικείμενα Length και να προκύπτει ένα νέο αντικείμενο. Αν και τα δύο αντικείμενα έχουν την ίδια μονάδα μήκους (είτε cm είτε in) το αποτέλεσμα να είναι σε αυτή τη μονάδα μήκους. Αν η μονάδα μήκους είναι διαφορετική ανάμεσα στα δύο αντικείμενα τότε το αποτέλεσμα να είναι σε εκατοστά. Δίνεται ότι 1 ίντσα ισούται με 2.54 εκατοστά. Ακολουθεί ένα παράδειγμα εκτέλεσης όπου αρχικά δημιουργείται ένα αντικείμενο Length (με μονάδα μήκους τα εκατοστά) και εκτυπώνεται με την __str__, μετά δημιουργείται ένα άλλο αντικείμενο Length (με μονάδα μήκους τις ίντσες) και εκτυπώνεται με την __repr__ και μετά με τον τελεστή + προστίθενται τα δύο αντικείμενα και δημιουργείται ένα νέο αντικείμενο:

$ python e4a7.py
Εκτύπωση αντικειμένου με τη μέθοδο __str__ (3 τρόποι)
5.50cm
5.50cm
5.50cm
Εκτύπωση αντικειμένου με τη μέθοδο __repr__ (2 τρόποι)
value=3.0 unit=in
value=3.0 unit=in
Υπερφόρτωση τελεστή +
5.50cm + 3.00in = 13.12cm
Λύση άσκησης E4A7
e4a7.py
class Length:
    def __init__(self, value, unit):
        self.value = value
        self.unit = unit

    def __str__(self):
        return f"{self.value:.2f}{self.unit}"

    def __repr__(self):
        return f"value={self.value} unit={self.unit}"

    def __add__(self, another_length):
        if self.unit == another_length.unit:
            return Length(self.unit, self.value + another_length.value)
        total_length = 0
        if self.unit == "in":
            total_length += self.value * 2.54
        else:
            total_length += self.value
        if another_length.unit == "in":
            total_length += another_length.value * 2.54
        else:
            total_length += another_length.value
        return Length(total_length, "cm")


def main():
    a = Length(5.5, "cm")
    b = Length(3.0, "in")

    print("Εκτύπωση αντικειμένου με τη μέθοδο __str__ (3 τρόποι)")
    print(a)
    print(str(a))
    print(a.__str__())

    print("Εκτύπωση αντικειμένου με τη μέθοδο __repr__ (2 τρόποι)")
    print(repr(b))
    print(b.__repr__())

    print("Υπερφόρτωση τελεστή +")
    c = a + b
    print(f"{a} + {b} = {c}")


if __name__ == "__main__":
    main()

Άσκηση 84 - Έστω ο ακόλουθος κώδικας που αφορά μια λίστα δραστηριοτήτων (TO DO LIST). Συμπληρώστε τον έτσι ώστε στις εντολές print να εμφανίζεται αυτό που έχει συμπεριληφθεί ως σχόλιο δεξιά της εντολής.

tdl = TodoList("groceries")
tdl.add("milk")
tdl.add("bread")
print(tdl.todos) # [description=milk completed=False, description=bread completed=False]
tdl.todos[0].toggle()
tdl.stats() # {'open': 1, 'completed': 1}
Λύση άσκησης E4A8
e4a8.py
class TodoList:
    def __init__(self, title):
        self.title = title
        self.todos = []

    def add(self, item_description):
        todo = Todo(item_description)
        self.todos.append(todo)

    def stats(self):
        stats_dict = {"open": 0, "completed": 0}
        for todo in self.todos:
            if todo.completed:
                stats_dict["completed"] += 1
            else:
                stats_dict["open"] += 1
        print(stats_dict)


class Todo:
    def __init__(self, description):
        self.description = description
        self.completed = False

    def toggle(self):
        self.completed = not self.completed

    def __repr__(self):
        return f"description={self.description} completed={self.completed}"


def main():
    tdl = TodoList("groceries")
    tdl.add("milk")
    tdl.add("bread")
    print(tdl.todos)
    tdl.todos[0].toggle()
    tdl.stats()


if __name__ == "__main__":
    main()

Άσκηση 95 - Δημιουργήστε μια κλάση ζαριού (Dice). Υλοποίήστε τα ακόλουθα:

  • Ο κατασκευαστής να δέχεται ένα όρισμα με όνομα probs που να είναι ένα λεξικό όπου κλειδί είναι ο αριθμός της πλευράς του ζαριού και τιμή είναι η πιθανότητα το ζάρι να έχει αυτό το αποτέλεσμα σε μια ρίψη του. Για παράδειγμα το λεξικό {1: 1/6, 2: 1/6, 3: 1/6, 4: 1/6, 5: 1/6, 6: 1/6} αντιστοιχεί σε ένα συνηθισμένο δίκαιο ζάρι.
  • Προσθέστε μια μέθοδο roll που να δέχεται ένα όρισμα με όνομα n με προκαθορισμένη τιμή 1 που να επιστρέφει τα αποτελέσματα από n ρίψεις του ζαριού.
  • Προσθέστε μια μέθοδο expected_value που να επιστρέφει την αναμενόμενη τιμή και ορίστε τη συνάρτηση ως @property.
  • Προσθέστε μια μέθοδο κλάσης from_sides που να δέχεται ως όρισμα το πλήθος των πλευρών ενός ζαριού και να επιστρέφει ένα αντικείμενο ζαριού με ίση πιθανότητα αποτελέσματος για κάθε πλευρά του (χρησιμοποιήστε τον decorator @classmethod).
  • Προσθέστε τη μέθοδο __len__ έτσι ώστε να εμφανίζει για ένα ζάρι το πλήθος των πλευρών του.
  • Υπερφορτώστε τον τελεστή + έτσι ώστε να δημιουργεί ένα νέο ζάρι με τις πιθανότητες να προκύψει το καθένα από τα αποτελέσματα από το συνδυασμό των αποτελέσματων δύο ζαριών. Για παράδειγμα προσθέτοντας δύο ζάρια με 6 πλευρές το καθένα θα προκύψει ένα νέο "ζάρι" με πιθανά αποτελέσματα από το 2 μέχρι το 12, όπου το 2 προκύπτει μόνο με (1,1) και έχει πιθανότητα 1/6 * 1/6 να συμβεί, το 3 μπορεί να προκύψει με (1,2) και (2,1) και έχει πιθανότητα 1/6 * 1/6 + 1/6 * 1/6 να συμβεί κ.ο.κ.
Λύση άσκησης E4A9
e4a9.py
import random


class Dice:
    def __init__(self, probs):
        self.probs = probs

    @classmethod
    def from_sides(cls, n=6):
        return Dice({i: 1 / n for i in range(1, n + 1)})

    def roll(self, n=1):
        sides = list(self.probs.keys())
        probabilities = list(self.probs.values())
        return random.choices(sides, weights=probabilities, k=n)

    @property
    def expected_value(self):
        return sum(i * p for i, p in self.probs.items())

    def __len__(self):
        return len(self.probs)

    def __add__(self, other):
        if not isinstance(other, Dice):
            raise ValueError("Only dice + dice is allowed!")
        new_probs = {}
        for s1, p1 in self.probs.items():
            for s2, p2 in other.probs.items():
                new_key = s1 + s2
                if new_key not in new_probs:
                    new_probs[new_key] = 0
                new_probs[new_key] += p1 * p2
        return Dice(new_probs)

    def __repr__(self):
        return f"Dice({self.probs})"

    def __str__(self):
        sides = ", ".join(map(str, self.probs.keys()))
        return f"Dice with sides: {sides}"


if __name__ == "__main__":
    d6 = Dice({1: 1 / 6, 2: 1 / 6, 3: 1 / 6, 4: 1 / 6, 5: 1 / 6, 6: 1 / 6})
    print(repr(d6))
    print(str(d6))
    print(f"Rolls: {d6.roll(5)}")
    print(f"Expected value of d6 = {d6.expected_value}, sides = {len(d6)}")

    d4 = Dice.from_sides(4)
    print(repr(d4))
    print(str(d4))
    print(f"Rolls: {d4.roll(5)}")
    print(f"Expected value of d4 = {d4.expected_value}, sides = {len(d4)}")

    d6_d4 = d6 + d4
    print(repr(d6_d4))
    print(str(d6_d4))
    print(f"Rolls: {d6_d4.roll(5)}")
    print(f"Expected value of d6_d4 = {d6_d4.expected_value}, sides = {len(d6_d4)}")

Παράδειγμα εκτέλεσης:

$ python e4a9.py
Dice({1: 0.16666666666666666, 2: 0.16666666666666666, 3: 0.16666666666666666, 4: 0.16666666666666666, 5: 0.16666666666666666, 6: 0.16666666666666666})
Dice with sides: 1, 2, 3, 4, 5, 6
Rolls: [2, 4, 1, 5, 6]
Expected value of d6 = 3.5, sides = 6
Dice({1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25})
Dice with sides: 1, 2, 3, 4
Rolls: [3, 1, 2, 4, 1]
Expected value of d4 = 2.5, sides = 4
Dice({2: 0.041666666666666664, 3: 0.08333333333333333, 4: 0.125, 5: 0.16666666666666666, 6: 0.16666666666666666, 7: 0.16666666666666666, 8: 0.125, 9: 0.08333333333333333, 10: 0.041666666666666664})
Dice with sides: 2, 3, 4, 5, 6, 7, 8, 9, 10
Rolls: [5, 8, 5, 5, 2]
Expected value of d6_d4 = 6.0, sides = 9

Άσκηση 10 Γράψτε ένα πρόγραμμα που να δέχεται ορίσματα γραμμής εντολών (τουλάχιστον 2). Αν το πρώτο όρισμα είναι το sum να εμφανίζει το άθροισμα από τις τιμές που ακολουθούν. Αν το πρώτο όρισμα είναι reverse τότε να αντιστρέφει το κείμενο που ακολουθεί.

Λύση άσκησης E4A10
e4a10.py
import sys

def main():
    if len(sys.argv) < 2:
        print("Παρακαλώ δώστε τουλάχιστον 2 ορίσματα")
        print("Χρήση: python program.py [sum|reverse] τιμή1 τιμή2 ...")
        return

    action = sys.argv[1] 

    if action == "sum":
        if len(sys.argv) < 3:
            print("Παρακαλώ δώστε τουλάχιστον έναν αριθμό για άθροισμα")
            return

        total = 0
        try:
            for num in sys.argv[2:]:
                total += float(num)
            print(f"Το άθροισμα είναι: {total}")
        except ValueError:
            print("Σφάλμα: Όλα τα ορίσματα πρέπει να είναι αριθμοί")

    elif action == "reverse":
        if len(sys.argv) < 3:
            print("Παρακαλώ δώστε κείμενο για αντιστροφή")
            return

        text = " ".join(sys.argv[2:])
        reversed_text = text[::-1]
        print(f"Αντεστραμμένο κείμενο: {reversed_text}")

    else:
        print(f"Άγνωστη εντολή: {action}")
        print("Διαθέσιμες εντολές: sum, reverse")


if __name__ == "__main__":
    main()

Παράδειγμα εκτέλεσης:

$ python e4a10.py sum 5 10 15
Το άθροισμα είναι: 30.0
$ python e4a10.py reverse Hello
olleH

Λύση άσκησης E4A10 με το argparse
e4a10b.py
import argparse

def main():
    parser = argparse.ArgumentParser(description="Process some inputs with different operations.")

    subparsers = parser.add_subparsers(dest='command', required=True, help='Available commands')

    sum_parser = subparsers.add_parser('sum', help='Sum all the provided numbers')
    sum_parser.add_argument('numbers', type=float, nargs='+', help='Numbers to sum')

    reverse_parser = subparsers.add_parser('reverse', help='Reverse the provided text')
    reverse_parser.add_argument('text', nargs='+', help='Text to reverse')

    args = parser.parse_args()

    if args.command == 'sum':
        total = sum(args.numbers)
        print(f"The sum is: {total}")
    elif args.command == 'reverse':
        reversed_text = ' '.join(args.text)[::-1]
        print(f"Reversed text: {reversed_text}")

if __name__ == "__main__":
    main()

Παράδειγμα εκτέλεσης:

$ python e4a10b.py -h
usage: e4a10b.py [-h] {sum,reverse} ...

Process some inputs with different operations.

positional arguments:
{sum,reverse}  Available commands
    sum          Sum all the provided numbers
    reverse      Reverse the provided text

options:
-h, --help     show this help message and exit
$ python e4a10b.py sum -h 
usage: e4a10b.py sum [-h] numbers [numbers ...]

positional arguments:
numbers     Numbers to sum

options:
-h, --help  show this help message and exit
$ python e4a10b.py reverse -h
usage: e4a10b.py reverse [-h] text [text ...]

positional arguments:
text        Text to reverse

options:
-h, --help  show this help message and exit
$ python e4a10b.py revers     
usage: e4a10b.py [-h] {sum,reverse} ...
e4a10b.py: error: argument command: invalid choice: 'revers' (choose from 'sum', 'reverse')
$ python e4a10b.py sum 10 20 30 
The sum is: 60.0
$ python e4a10b.py reverse Hello
Reversed text: olleH

Άσκηση 11 Γράψτε ένα πρόγραμμα που να ρυθμίζει το logging έτσι ώστε να εμφανίζει μηνύματα INFO στην οθόνη (με format %(levelname)s - %(message)s) και να αποθηκεύει τα μηνύματα (από DEBUG και πάνω) σε αρχείο e4a11.log (με format %(asctime)s - %(levelname)s - %(message)s). Υλοποιήστε μια συνάρτηση calculate_division(x, y) που: (1) καταγράφει DEBUG με τις τιμές εισόδου, (2) επιστρέφει το αποτέλεσμα της διαίρεσης (INFO για επιτυχία), (3) καταγράφει ERROR με stack trace αν y=0. Καλέστε τη συνάρτηση δύο φορές (έγκυρη και μη έγκυρη διαίρεση) και επαληθεύστε τα logs στην οθόνη και στο αρχείο.

Λύση άσκησης E4A11
e4a11.py
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(levelname)s - %(message)s",
    handlers=[logging.StreamHandler(), logging.FileHandler("e4a11.log", mode="w")],
)

logger = logging.getLogger()
logger.handlers[1].setFormatter(
    logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
)
logger.handlers[1].setLevel(logging.DEBUG)


def calculate_division(x, y):
    logger.debug(f"Calculating {x} / {y}")
    try:
        result = x / y
        logger.info(f"Result: {result}")
        return result
    except ZeroDivisionError:
        logger.exception("Division by zero!")
        return None


calculate_division(10, 2)
calculate_division(5, 0)

Παράδειγμα εκτέλεσης

$ python e4a11.py
INFO - Result: 5.0
ERROR - Division by zero!
Traceback (most recent call last):
File "/Users/chgogos/git_repos/dituoi_ARCHES_GLOSSON_PROGRAMMATISMOU/docs/src/python/lab4/e4a11.py", line 20, in calculate_division
    result = x / y
ZeroDivisionError: division by zero