Vai al contenuto

Programmazione a oggetti

In questa lezione impari a

  • creare una classe con attributi e metodi
  • proteggere i dati con l'incapsulamento
  • riusare il codice con l'ereditarietà
  • capire il polimorfismo

Finora hai tenuto separati i dati (variabili, liste, dizionari) e le operazioni (funzioni). La programmazione a oggetti (OOP, Object Oriented Programming) li mette insieme: un oggetto contiene sia i suoi dati sia le azioni che sa fare.

Classi e oggetti

Una classe è il progetto, un oggetto è una cosa costruita da quel progetto. La classe Auto descrive com'è fatta un'auto; la tua Panda e la Golf del vicino sono due oggetti di quella classe.

class Persona:
    def __init__(self, nome, eta):
        self.nome = nome          # attributi: i dati dell'oggetto
        self.eta = eta

    def presentati(self):         # metodo: un'azione dell'oggetto
        print(f"Ciao, sono {self.nome} e ho {self.eta} anni")

anna = Persona("Anna", 16)        # crea un oggetto
luca = Persona("Luca", 17)

anna.presentati()
luca.presentati()
print(anna.nome)
  • __init__ è il costruttore: viene eseguito quando crei l'oggetto e ne imposta i dati iniziali.
  • self indica l'oggetto stesso: self.nome è "il nome di questo oggetto". Va sempre messo come primo parametro dei metodi.

Incapsulamento: proteggere i dati

Alcuni dati non devono essere modificati liberamente. Il saldo di un conto in banca, per esempio, deve cambiare solo con versamenti e prelievi controllati.

Scrittura Significato
self.nome pubblico: accessibile da tutti
self._nome protetto: per convenzione, "non toccare da fuori"
self.__nome privato: Python lo nasconde davvero
class ContoBancario:
    def __init__(self, titolare):
        self.titolare = titolare
        self.__saldo = 0

    def versa(self, importo):
        if importo <= 0:
            print("Importo non valido")
            return
        self.__saldo += importo

    def preleva(self, importo):
        if importo > self.__saldo:
            print("Saldo insufficiente")
            return
        self.__saldo -= importo

    def saldo(self):
        return self.__saldo

conto = ContoBancario("Marta")
conto.versa(100)
conto.preleva(150)        # Saldo insufficiente
print(conto.saldo())      # 100

Dall'esterno nessuno può scrivere conto.__saldo = 1000000: il saldo cambia solo attraverso i metodi, che controllano che l'operazione sia valida.

Ereditarietà: riusare il codice

Una classe può ereditare da un'altra: riceve tutti i suoi attributi e metodi e può aggiungerne di nuovi.

class Studente(Persona):
    def __init__(self, nome, eta, classe):
        super().__init__(nome, eta)    # usa il costruttore di Persona
        self.classe = classe

    def presentati(self):
        super().presentati()
        print(f"Frequento la {self.classe}")

marta = Studente("Marta", 16, "3B")
marta.presentati()

Studente è una sottoclasse di Persona. super() richiama i metodi della classe "genitore".

Polimorfismo: stesso metodo, comportamenti diversi

Classi diverse possono avere un metodo con lo stesso nome, ciascuna con il suo comportamento.

class Animale:
    def __init__(self, nome):
        self.nome = nome

    def verso(self):
        return "..."

class Cane(Animale):
    def verso(self):
        return "Bau!"

class Gatto(Animale):
    def verso(self):
        return "Miao!"

class Mucca(Animale):
    def verso(self):
        return "Muu!"

fattoria = [Cane("Fido"), Gatto("Micia"), Mucca("Carolina")]
for animale in fattoria:
    print(f"{animale.nome}: {animale.verso()}")

Il ciclo non deve sapere che tipo di animale sta trattando: chiama verso() e ciascuno risponde a modo suo.

Esercizi

Esercizio 1 – La classe Studente. Crea una classe Studente con nome e una lista di voti, un metodo aggiungi_voto(voto) e un metodo media().

Mostra la soluzione
class Studente:
    def __init__(self, nome):
        self.nome = nome
        self.voti = []

    def aggiungi_voto(self, voto):
        if 1 <= voto <= 10:
            self.voti.append(voto)

    def media(self):
        if not self.voti:
            return 0
        return sum(self.voti) / len(self.voti)

s = Studente("Ivan")
s.aggiungi_voto(7)
s.aggiungi_voto(9)
print(f"{s.nome}: media {s.media():.1f}")   # Ivan: media 8.0

Esercizio 2 – L'auto. Crea una classe Auto con marca, modello e chilometri. Aggiungi un metodo viaggia(km) che aumenta i chilometri (rifiutando valori negativi) e un metodo scheda() che stampa i dati.

Mostra la soluzione
class Auto:
    def __init__(self, marca, modello, km=0):
        self.marca = marca
        self.modello = modello
        self.km = km

    def viaggia(self, km):
        if km < 0:
            print("I chilometri non possono essere negativi")
            return
        self.km += km

    def scheda(self):
        print(f"{self.marca} {self.modello} - {self.km} km")

panda = Auto("Fiat", "Panda", 12000)
panda.viaggia(350)
panda.scheda()   # Fiat Panda - 12350 km

Sfida: il garage virtuale

Crea una classe Garage che contiene una lista di oggetti Auto, con i metodi parcheggia(auto), ritira(modello), elenco() e km_totali(). Poi aggiungi una sottoclasse AutoElettrica con l'autonomia della batteria e un metodo ricarica().