Esplora il progetto in dettaglio.
Il Gestionale Zoo è un progetto sviluppato in Python per esercitarsi con programmazione a oggetti e gestione dei dati. Permette di organizzare animali e zone, offrendo un’interfaccia testuale chiara e funzionalità modulari.


Principali funzionalità
Gestione Animali
Permette di creare e gestire animali con attributi specifici (nome, specie, zampe, ambiente, ecc.), organizzandoli in zone dello zoo.
Interfaccia CLI
Offre un’interazione testuale intuitiva: l’utente può inserire, modificare o eliminare animali e visualizzarne i dettagli in maniera semplice.
Architettura Modulabile
Il codice è suddiviso in più file (animali.py, zoo.py, gui.py) per mantenere separati i modelli, la logica e l’interfaccia, facilitando manutenzione e riuso.
Salvataggio Dati
Supporta l’esportazione e il caricamento dei dati su file, garantendo persistenza delle informazioni inserite.
Galleria del progetto
Qui trovi una selezione di screenshot che illustrano l’interfaccia e le funzionalità dell’applicazione.






Come funziona Gestionale Zoo
Passo 1: Avvio
L’utente avvia il programma in Python e accede alla CLI (Command Line Interface), predisposta per interagire con i moduli dedicati alla gestione.
Passo 2: Gestione Animali
È possibile aggiungere, modificare o rimuovere animali, ognuno con attributi specifici (specie, zampe, ambiente, ecc.), organizzandoli per zone.
Passo 3: Salvataggio ed Esportazione
Le modifiche vengono salvate su file esterni, così da garantire persistenza dei dati e possibilità di riaprire lo zoo senza perdere le informazioni.
Codice
zoo.py
Codice completo
from animali import *
"""
Creare una classe Zoo
STATO:
- zoo: dizionario che ha come chiavi i nomi degli animali e come valori coppie (animale, zona),
dove animale è un oggetto Animale o sottoclasse e zona è una stringa composta da una lettera maiuscola
seguita da un numero intero (es. "A1")
METODI:
Oltre a metodi specificati sotto:
- costruttore che inizializza il dizionario
- __str__: restituisce una stringa rappresentante lo zoo sfruttando gli __str__ degli animali, ed esempio:
"Zona A1: Alex Il Leone (Mammifero) Alex ha 4 zampe, sangue caldo e vive in Terra facendo un Ruggito,
con 7 settimane di gestazionen
Zona Z2: Kowalski Il Pinguino (Uccello) Kowalski ha 2 zampe, sangue caldo e vive in Terra e Acqua facendo
un Garrito, non sa volaren
Zona J7: Nemo Il PescePagliaccio (Pesce) Nemo è senza zampe, sangue freddo e vive in Acqua Salatan"
- __eq__: uguaglianza profonda fra zoo
"""
# (nome, classe, specie, numero_zampe, sangue_caldo, ambiente, zona, verso)
# Funzioni ausiliarie
# Verifica che il parametro "classe" sia una stringa valida e appartenga a una lista di classi valide
def parametro_classe(classe):
if not isinstance(classe, str):
raise TypeError("La classe deve essere una stringa")
if classe not in ["Mammifero", "Uccello", "Pesce", "Rettile", "Anfibio"]:
raise ValueError("La classe deve essere una stringa valida tra: Mammifero, Uccello, Pesce, Rettile, Anfibio.")
# Verifica che il parametro "specie" sia una stringa e appartenga a una lista di specie valide
def parametro_specie(specie):
if not isinstance(specie, str):
raise TypeError("La specie deve essere una stringa.")
if specie not in ["Leone", "Giraffa", "Ippopotamo", "Pinguino", "Gufo", "PescePagliaccio"]:
raise ValueError("La specie deve essere una stringa valida tra: Leone, Giraffa, Ippopotamo, Pinguino, "
"Gufo, PescePagliaccio.")
# Verifica che la zona sia una stringa formata due caratteri: maiuscola e numero
def parametro_zona(zona):
if not isinstance(zona, str):
raise TypeError("zona deve essere una stringa")
if len(zona) != 2 or not ('A' <= zona[0] <= 'Z') or not ('0' <= zona[1] <= '9'):
raise ValueError("Il parametro 'zona' deve essere una stringa composta da una lettera maiuscola seguita da un"
" numero.")
class Zoo:
# Inizializza un dizionario vuoto per memorizzare gli animali e le loro zone
def __init__(self):
self.zoo = {}
# Restituisce una stringa che elenca tutti gli animali, una riga per ciascun animale,
# in formato esempio "Zona A1: stringa rappresentativa dell'animalenZona A2:..."
# Usa __str__ dell'animale per la descrizione
def __str__(self):
# Accumula le descrizioni degli animali
risultato = []
# Itero su ogni voce: chiave=nome, valore=(animale, zona)
for nome, (animale, zona) in self.zoo.items():
risultato.append("Zona " + zona + ": " + str(animale))
# Unisco le righe con un a capo
return "n".join(risultato)
def __eq__(self, altro):
"""Uguaglianza profonda tra due zoo."""
# Controlla se l'altro oggetto è un'istanza della classe Zoo
if not isinstance(altro, Zoo):
return False
# Verifica che le chiavi (i nomi degli animali) dei due dizionari siano le stesse
if self.zoo.keys() != altro.zoo.keys():
return False
# Verifica che i valori (coppie (animale, zona)) dei due dizionari siano gli stessi
return self.zoo == altro.zoo
def inserisci(self, animale, zona):
"""
Inserisce un nuovo animale (evitando duplicati) nel dizionario zoo controllando che i tipi dei
parametri attuali abbiano il tipo corretto (specificato fra parentesi) e siano
nel range di valori ammessi senza modificare maiuscole e minuscole
:param self: Riferimento all'istanza della classe
:param animale: oggetto di tipo Animale
:param zona: zona in cui si trova (stringa) composta da una lettera maiuscola seguita da un numero intero
(es. "A1")
:raise TypeError: se i parametri non hanno il tipo corretto
:raise ValueError: se i parametri non sono nel range di valori ammessi
+ :raise KeyError: se esiste già un animale con lo stesso nome
"""
# Controlla che 'animale' sia un oggetto Animale
if not isinstance(animale, Animale):
raise TypeError("Il parametro 'animale' deve essere un oggetto della classe Animale di tipo Animale o "
"sue sottoclassi")
parametro_zona(zona) # Controlla che la zona sia valida - raise Type e ValueError
# Verifica che non esista già un animale con lo stesso nome
if animale.nome in self.zoo:
raise KeyError("Un animale con questo nome esiste già nello zoo.")
# Aggiunge l'animale al dizionario con il nome come chiave e (animale, zona) come valore
self.zoo[animale.nome] = (animale, zona)
def animale(self, nome):
"""
Restituisce l'oggetto animale con nome "nome"
:return a: l'oggetto Animale
:raise: KeyError se l'animale non è presente nel dizionario
"""
# Controlla se l'animale è presente nello zoo
if nome not in self.zoo:
raise KeyError("Animale non trovato nello zoo")
# Variabile per ottenere la tupla associata al nome
# Accede al dizionario 'zoo' utilizzando il nome dell'animale come chiave
# e ottiene il valore associato, che è una tupla (animale, zona).
tupla_animale = self.zoo[nome]
# Restituisce l'oggetto animale (primo elemento della tupla)
a = tupla_animale[0]
return a
def zona(self, nome):
"""
Restituisce la zona in cui si trova l'animale con nome "nome"
:return: la zona in cui si trova l'animale
:raise: KeyError se l'animale non è presente nel dizionario
"""
if nome not in self.zoo:
raise KeyError("Animale non trovato nello zoo")
tupla_animale = self.zoo[nome]
# Restituisce la zona (secondo elemento della tupla)
zona = tupla_animale[1]
return zona
def elimina(self, nome):
"""
Elimina l'animale con nome "nome" dal dizionario zoo
:param self: Riferimento all'istanza della classe
:param nome: il nome dell'animale da eliminare
:raise: KeyError se l'animale non è presente nel dizionario
"""
if nome not in self.zoo:
raise KeyError("Animale non trovato nello zoo")
# Rimuove l'animale dal dizionario 'self.zoo' utilizzando la chiave 'nome'
del self.zoo[nome]
def cambia_zona(self, nome, zona):
"""
Cambia la zona dell'animale con nome "nome"
:param self: riferimento all'istanza della classe
:param nome: il nome dell'animale da spostare
:param zona: la nuova zona in cui si trova l'animale
:raise KeyError se l'animale non è presente nel dizionario
:raise ValueError se la zona non è nel formato corretto
:raise TypeError se la zona non è del tipo corretto
"""
if nome not in self.zoo:
raise KeyError("Animale non trovato nello zoo")
parametro_zona(zona) # Controlla la validità della nuova zona
tupla_animale = self.zoo[nome]
# Ottiene l'oggetto Animale dal primo elemento della tupla
animale_esistente = tupla_animale[0]
# Aggiorna la zona nella tupla associata alla chiave 'nome' nel dizionario 'zoo'
self.zoo[nome] = (animale_esistente, zona)
# Conferma lo spostamento avvenuto con successo
return True
def zone(self):
"""
Restituisce la lista delle zone presenti nel dizionario zoo
:return: la lista delle zone presenti nel dizionario zoo
"""
zone_presenti = [] # Inizializzo una lista vuota per raccogliere le zone uniche
# Itero su tutte le zone del dizionario zoo, dove ogni valore è una coppia (animale, zona)
for animale, zona in self.zoo.values():
if zona not in zone_presenti:
# Aggiunge la zona alla lista solo se non è già presente
zone_presenti.append(zona)
# Restituisce la lista ordinata delle zone uniche
return sorted(zone_presenti)
# Funzione ausiliaria per filtrare gli animali in base a un criterio specifico (es. classe, specie, ecc.)
def filtra_animali(self, indice, valore):
"""
Filtra gli animali nel dizionario zoo in base al valore specificato in un determinato indice della tupla
:param self: riferimento all'istanza della classe
:param indice: l'indice nella tupla su cui filtrare (es. 0 per l'animale, 1 per la zona)
:param valore: il valore da confrontare
:return: Una lista dei nomi degli animali che soddisfano il criterio.
"""
# Inizializza la lista dei risultati
risultato = []
# Itero su tutti gli elementi del dizionario zoo, dove ogni chiave del dizionario è il "nome" dell'animale,
# mentre il valore associato è una tupla (animale, zona)
for nome, (animale, zona) in self.zoo.items():
# Se filtro in base all'oggetto 'animale' della tupla
if indice == 0:
# Verifico se 'valore' è un oggetto 'type' (es. Mammifero)
if isinstance(valore, type):
# Verifico se 'animale' è un'istanza di quella classe
if isinstance(animale, valore):
risultato.append(nome)
# Controllo se 'valore' è una stringa che indica la 'specie' (es. "Leone"), lo confronto con
# 'animale.specie'
elif animale.specie == valore:
risultato.append(nome)
# Se filtro in base alla parte 'zona' della tupla, 'valore' sarà una stringa che indica la zona (es. "A1")
elif indice == 1:
if zona == valore:
risultato.append(nome)
return risultato
def animali_zona(self, zona):
"""
Restituisce la lista dei nomi degli animali presenti nella zona "zona"
:param self: Riferimento all'istanza della classe
:param zona: la zona di interesse
:return: la lista degli animali presenti nella zona "zona"
"""
parametro_zona(zona)
# Utilizzo la funzione interna per filtrare in base alla zona
# L'indice 1 rappresenta la posizione della zona nella tupla (animale, zona)
return self.filtra_animali(1, zona)
def zone_animali(self):
"""
Restituisce un dizionario che ha come chiavi le zone e come valori la lista degli animali presenti in quella
zona
Ad esempio, posso restituire il dizionario
{'A1': ['Alex'], 'A2': ['Skipper', 'Kowalski']}
:return: il dizionario con le zone e gli animali presenti in ciascuna zona
"""
dizionario_zone = {} # Inizializza un dizionario vuoto per le zone
for nome, (animale, zona) in self.zoo.items():
# Controlla se la zona è già una chiave nel dizionario, se non lo è, crea una nuova lista per la zona
if zona not in dizionario_zone:
dizionario_zone[zona] = []
# Aggiungo il nome dell'animale alla lista della zona
dizionario_zone[zona].append(nome)
return dizionario_zone
# Ho definito Mammifero, Uccello e Pesce come richiesto in animali.py - non ho definito altre classi
def animali_classe(self, classe):
"""
Restituisce la lista dei nomi degli animali della classe "classe" (es. Mammifero)
Per "classe" è intesa una fra quelle al secondo livello della gerarchia (Mammifero, Uccello, Rettile, Anfibio,
Pesce)
:param self: Riferimento all'istanza della classe
:param classe: stringa rappresentante la classe di interesse
:raise ValueError se la classe non è fra quelle esistenti
:raise TypeError se la classe non è del tipo corretto
:return: la lista degli animali della classe "classe"
"""
parametro_classe(classe)
# Associo la stringa 'classe' a un tipo di classe
classi_valide = {
"Mammifero": Mammifero,
"Uccello": Uccello,
"Pesce": Pesce
}
tipo_classe = classi_valide.get(classe)
# Uso la funzione di filtraggio per trovare gli animali che appartengono alla classe specificata
# L'indice 0 si riferisce all'oggetto animale nella tupla (animale, zona)
return self.filtra_animali(0, tipo_classe)
def animali_specie(self, specie):
"""
Restituisce la lista dei nomi degli animali della specie "specie" (es. Leone)
:param self: Riferimento all'istanza della classe
:param specie: stringa rappresentante la specie di interesse
:raise TypeError se la specie non è del tipo corretto
:return: la lista degli animali della specie "specie"
NOTA: specie non solleva ValueError
"""
parametro_specie(specie)
return self.filtra_animali(0, specie)
def animali_ambiente(self, ambiente):
"""
Restituisce la lista dei nomi degli animali che vivono nell'ambiente "ambiente" (es. Terra)
:param self: Riferimento all'istanza della classe
:param ambiente: l'ambiente di interesse
:raise ValueError se l'ambiente non è nel formato corretto
:raise TypeError se l'ambiente non è del tipo corretto
:return: la lista degli animali che vivono nell'ambiente "ambiente"
"""
# Se l'ambiente è una stringa, la metto dentro una lista
if isinstance(ambiente, str):
ambiente = [ambiente]
elif not isinstance(ambiente, list):
raise TypeError("L'ambiente deve essere una stringa o una lista di stringhe.")
# Controllo iterando che ogni elemento sia una stringa
for a in ambiente:
if not isinstance(a, str):
raise TypeError("Ogni elemento della lista 'ambiente' deve essere una stringa.")
# Controllo che almeno uno dei valori sia Terra, Acqua, Aria
ambienti_validi = {"Terra", "Acqua", "Aria"}
if not any(a in ambienti_validi for a in ambiente):
raise ValueError("L'ambiente deve contenere almeno uno tra: Terra, Acqua, Aria.")
# Inizializzo una lista vuota per raccogliere i nomi degli animali
risultato = []
# Itero su tutti gli animali e verifico se in animale.ambiente c'è almeno uno degli ambienti
for nome, (animale, zona) in self.zoo.items():
if any(a in animale.ambiente for a in ambiente):
risultato.append(nome)
return risultato
def animali_con_zampe_almeno(self, numero_zampe=2):
"""
Restituisce la lista dei nomi degli animali che hanno almeno "numero_zampe" zampe
:param self: Riferimento all'istanza della classe
:param numero_zampe: intero non negativo rappresentante il numero minimo di zampe (default=2)
:raise ValueError se il numero di zampe non ha un valore accettato
:raise TypeError se il numero di zampe non è del tipo corretto
:return: la lista degli animali che hanno almeno "numero_zampe" zampe
"""
parametro_numero_zampe(numero_zampe)
# Inizializza una lista vuota per raccogliere i nomi degli animali
risultato = []
for nome, (animale, zona) in self.zoo.items():
if animale.numero_zampe >= numero_zampe:
risultato.append(nome)
return risultato
def conta_sangue(self, sangue_caldo):
"""
Restituisce il numero di animali con sangue caldo o freddo
:param self: Riferimento all'istanza della classe
:param sangue_caldo: True se si vogliono contare gli animali con sangue caldo, False altrimenti
:raise TypeError se sangue_caldo non è del tipo corretto
:return: il numero di animali con sangue caldo o freddo
"""
parametro_sangue_caldo(sangue_caldo)
conteggio = 0
for nome, (animale, zona) in self.zoo.items():
if animale.sangue_caldo == sangue_caldo:
conteggio += 1
return conteggio
def conta_classe(self, classe):
"""
Restituisce il numero di animali della classe "classe" (es. Mammifero)
:param self: Riferimento all'istanza della classe
:param classe: stringa rappresentante la classe di interesse
:raise ValueError se la classe non è fra quelle esistenti
:raise TypeError se la classe non è del tipo corretto
:return: il numero di animali della classe "classe"
"""
parametro_classe(classe)
# Uso la funzione animali_classe per ottenere gli animali di una determinata classe e conto il numero con len
return len(self.animali_classe(classe))
def conta_specie(self, specie):
"""
Restituisce il numero di animali della specie "specie"
:param self: Riferimento all'istanza della classe
:param specie:stringa rappresentante la specie di interesse
:raise TypeError se la specie non è del tipo corretto
:return: il numero di animali della specie "specie"
"""
parametro_specie(specie)
return len(self.animali_specie(specie)) # Uso animali_specie per il conteggio
"""
Non catturare le eccezioni di lettura e scrittura del file, lasciale propagare e catturarle nella GUI
Solo nella carica: se il file non è nel formato corretto, sollevare ValueError
"""
def salva(self, nome_file):
"""
Salva lo stato dell'oggetto Zoo in un file di testo
:param self: Riferimento all'istanza della classe
:param nome_file: stringa rappresentante il nome del file in cui salvare lo stato
"""
# Salva ogni animale con: nome;classe;specie;numero_zampe;sangue_caldo;ambiente;zona;verso
# 'ambiente' sarà salvato come una singola stringa, unita da " e "
# Apre il file in modalità scrittura ("w"). Se il file esiste, verrà sovrascritto,
# se non esiste, ne verrà creato uno nuovo
with open(nome_file, "w") as file:
# Itero su tutte le coppie chiave (= nome), valore (=(animale,zona)) del dizionario 'self.zoo'
for nome, (animale, zona) in self.zoo.items():
# Ricavo i dati dall'oggetto animale
nome_a = animale.get_nome()
# Determino la classe animale (Mammifero, Uccello, Pesce) in base all'istanza
# Se è un Mammifero, classe_a = "Mammifero"; se Uccello, "Uccello"; se Pesce, "Pesce"
# Se non rientra in queste classi sollevo ValueError
if isinstance(animale, Mammifero):
classe_a = "Mammifero"
elif isinstance(animale, Uccello):
classe_a = "Uccello"
elif isinstance(animale, Pesce):
classe_a = "Pesce"
else:
raise ValueError("Classe animale non gestita.")
specie_a = animale.specie
zampe_a = str(animale.get_numero_zampe())
sangue_a = str(animale.get_sangue_caldo())
ambiente_a = " e ".join(animale.get_ambiente())
zona_a = zona
verso_a = animale.get_verso()
# Costruisco la riga da salvare nel file
linea_da_salvare = (nome_a + ";" + classe_a + ";" + specie_a + ";" +
zampe_a + ";" + sangue_a + ";" +
ambiente_a + ";" + zona_a + ";" + verso_a)
# Scrive la riga - ogni riga separata da capo
file.write(linea_da_salvare + "n")
def carica(self, nome_file):
"""
Carica lo stato dell'oggetto Zoo da un file di testo, sovrascrivendo lo stato corrente
se il caricamento va a buon fine
:param self: Riferimento all'istanza della classe
:param nome_file: stringa rappresentante il nome del file da cui caricare lo stato
:raise ValueError se il file non è nel formato corretto
"""
# Ricarica i dati di ogni animale, sovrascrivendo lo stato attuale
# Si aspetta linee con 8 campi separati da ";": nome;classe;specie;numero_zampe;sangue_caldo;ambiente;zona;verso
# 'ambiente' sarà una stringa con i vari ambienti uniti da " e "
self.zoo = {} # Svuota lo zoo corrente per evitare di sovrapporre animali vecchi a quelli che leggiamo dal file
# Apre il file in modalità lettura ("r")
with open(nome_file, "r") as file:
# Itero su ogni riga del file e rimuovo spazi e nuove linee da inizio e fine
# e divido la riga in parti usando ";" come separatore
for linea in file:
parti = linea.strip().split(";")
# Controllo che siano 8 campi esatti:
# nome, classe, specie, numero_zampe, sangue_caldo, ambiente, zona, verso
if len(parti) != 8:
raise ValueError("Formato del file non valido.")
# Assegno a variabili locali i campi
(nome_a, classe_a, specie_a,
zampe_a, sangue_a, ambiente_a,
zona_a, verso_a) = parti
# Verifico che i parametri siano validi
parametro_classe(classe_a)
parametro_specie(specie_a)
parametro_zona(zona_a)
# Provo a convertire il numero di zampe 'zampe_a' in un intero, se non convertibile sollevo ValueError
try:
zampe_a = int(zampe_a)
except ValueError:
raise ValueError("numero_zampe non valido.")
# Converto 'sangue_a' da stringa a bool
if sangue_a == "True":
sangue_a = True
elif sangue_a == "False":
sangue_a = False
else:
raise ValueError("sangue_caldo deve essere 'True' o 'False'.")
# 'ambiente_a' era una stringa unita da " e ", la risuddivido in una lista
# se la stringa è vuota, la lista sarà vuota altrimenti divide la stringa 'ambiente_a' in base al
# separatore " e ", rimuove eventuali spazi iniziali e finali da ciascun elemento e crea una
# lista contenente gli ambienti
if ambiente_a.strip() == "":
lista_ambienti = []
else:
lista_ambienti = [x.strip() for x in ambiente_a.split(" e ")]
# In base alla classe e specie, costruisco l'oggetto animale richiamando il costruttore corrispondente
if classe_a == "Mammifero":
if specie_a == "Leone":
# Il costruttore di Leone si aspetta (nome, sangue_caldo, zampe, verso, ambiente, sett_gest)
animale = Leone(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, 7)
elif specie_a == "Giraffa":
animale = Giraffa(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, 65)
elif specie_a == "Ippopotamo":
animale = Ippopotamo(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, 34)
else:
raise ValueError("Specie di Mammifero non valida.")
elif classe_a == "Uccello":
if specie_a == "Pinguino":
# Il costruttore di Pinguino si aspetta (nome, sangue_caldo, zampe, verso, ambiente, volatile)
animale = Pinguino(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, False)
elif specie_a == "Gufo":
animale = Gufo(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, True)
else:
raise ValueError("Specie di Uccello non valida.")
elif classe_a == "Pesce":
if specie_a == "PescePagliaccio":
# (nome, sangue_caldo, zampe, verso, ambiente, acqua)
animale = PescePagliaccio(nome_a, sangue_a, zampe_a, verso_a, lista_ambienti, "Salata")
else:
raise ValueError("Specie di Pesce non valida.")
else:
raise ValueError("Classe animale non gestita.")
# Inserisco nel dizionario 'self.zoo' l'animale [la chiave (= nome_a) e il valore = (animale, zona_a)]
self.zoo[nome_a] = (animale, zona_a)
animali.py
Codice completo
# Definire una gerarchia di classi per rappresentare animali invertebrati
# Guardare il file main.py come riferimento l'uso dei metodi
"""Classe Animale
STATO:
- nome: stringa non vuota
- sangue_caldo: booleano
- numero_zampe: intero non negativo
- verso: stringa
- ambiente: lista di stringhe contenente almeno uno fra "Terra", "Acqua", "Aria"
METODI:
- costruttore che prende gli attributi dello stato in input: controlla opportunamente tipi e valori,
eventualmente sollevando eccezioni TypeError e ValueError
- getters: metodi per restituire gli attributi dello stato singolarmente
- setters: metodi per modificare gli attributi dell'animale singolarmente, controlla opportunamente tipi e valori,
eventualmente sollevando eccezioni TypeError e ValueError
- __str__: restituisce una stringa rappresentante l'animale, ad esempio "Tizio ha 4 zampe,
sangue caldo e vive in Terra facendo un Ruggito".
Notare l'assenza del carattere a capo alla fine della stringa (n)
Gli animali con più ambienti dovranno restituire la lista di ambienti congiunti da " e ",
ad esempio "vive in Terra e Acqua".
Gli animali senza verso dovranno fermare la stringa all'ambiente, ad esempio "Tizio ha 4 zampe,
sangue caldo e vive in Acqua".
Gli animali con 0 zampe dovranno avere la stringa "è senza zampe" al posto di "ha 0 zampe", ad esempio
"Tizio è senza zampe, sangue caldo e vive in Terra facendo un Ruggito".
- __eq__: uguaglianza profonda fra animali
"""
# Controlli dei parametri
# Eseguo tutti i controlli separati in diverse funzioni che saranno richiamate nelle funzioni successive, quando servono
# Verifica che il parametro "nome" sia una stringa non vuota (lunghezza > 0)
def parametro_nome(nome):
if not isinstance(nome, str):
raise TypeError("Il nome deve essere una stringa.")
if not len(nome) > 0:
raise ValueError("Il nome deve essere una stringa non vuota.")
# Verifica che il parametro "sangue_caldo" sia di tipo booleano
def parametro_sangue_caldo(sangue_caldo):
if not isinstance(sangue_caldo, bool):
raise TypeError("Sangue_caldo deve essere un valore booleano.")
# Verifica che il parametro "numero_zampe" sia un intero positivo
def parametro_numero_zampe(numero_zampe):
if not isinstance(numero_zampe, int):
raise TypeError("Il numero di zampe deve essere un intero.")
if numero_zampe < 0:
raise ValueError("Il numero di zampe deve essere un intero positivo.")
# Verifica che il parametro "verso" sia una stringa
def parametro_verso(verso):
if not isinstance(verso, str):
raise TypeError("Il verso deve essere una stringa.")
# Verifica che il parametro "ambiente" in cui vive (lista di stringhe) sia almeno uno fra: "Acqua", "Terra", "Aria"
def parametro_ambiente(ambiente):
"""
Verifica che l'ambiente sia una lista di stringhe,
e che contenga almeno uno fra 'Acqua', 'Terra', 'Aria'.
:param ambiente: lista o stringa che rappresenta gli ambienti
:raise TypeError: se l'ambiente non è una lista
:raise TypeError: se un elemento della lista di ambienti non è una stringa
:raise ValueError: se la lista non contiene almeno uno fra Acqua, Terra, Aria
"""
if not isinstance(ambiente, list):
raise TypeError("L'ambiente deve essere una lista di stringhe.")
# Itero su tutti gli elementi della lista per verificarne il tipo
for i in ambiente:
if not isinstance(i, str):
raise TypeError("Ogni elemento dell'ambiente deve essere una stringa.")
if "Acqua" not in ambiente and "Terra" not in ambiente and "Aria" not in ambiente:
raise ValueError("L'ambiente deve essere uno tra: Acqua, Terra, Aria.")
# Classe base Animale
class Animale:
# Costruttore per inizializzare l'oggetto Animale
def __init__(self, nome, sangue_caldo, numero_zampe, verso, ambiente):
# Eseguo i controlli di validazione sui parametri
parametro_nome(nome)
parametro_sangue_caldo(sangue_caldo)
parametro_numero_zampe(numero_zampe)
parametro_verso(verso)
parametro_ambiente(ambiente)
# Se i controlli passano, assegno i parametri agli attributi di istanza
self.nome = nome
self.sangue_caldo = sangue_caldo
self.numero_zampe = numero_zampe
self.verso = verso
self.ambiente = ambiente
# Inizializzo l'attributo 'specie' a None
self.specie = None
# getters - restituiscono nome, sangue, numero zampe, verso, ambiente
def get_nome(self):
return self.nome
def get_sangue_caldo(self):
return self.sangue_caldo
def get_numero_zampe(self):
return self.numero_zampe
def get_verso(self):
return self.verso
def get_ambiente(self):
return self.ambiente
# setters per modificare nome, sangue_caldo, numero_zampe, verso, ambiente
# per ognuno controllo che i tipi e valori siano validi, se lo sono, aggiorno l'attributo
def set_nome(self, nome):
parametro_nome(nome)
self.nome = nome
def set_sangue_caldo(self, sangue_caldo):
parametro_sangue_caldo(sangue_caldo)
self.sangue_caldo = sangue_caldo
def set_numero_zampe(self, numero_zampe):
parametro_numero_zampe(numero_zampe)
self.numero_zampe = numero_zampe
def set_verso(self, verso):
parametro_verso(verso)
self.verso = verso
def set_ambiente(self, ambiente):
parametro_ambiente(ambiente)
self.ambiente = ambiente
# Metodo __str__: restituisce una stringa rappresentante l'oggetto Animale
# Gli animali con più ambienti dovranno restituire la lista di ambienti congiunti da " e "
# Gli animali senza verso dovranno fermare la stringa all'ambiente
# Gli animali con 0 zampe dovranno avere la stringa "è senza zampe" al posto di "ha 0 zampe"
def __str__(self):
# Verifica se ci sono più ambienti e li concatena
if len(self.ambiente) > 1:
ambiente_str = " e ".join(self.ambiente)
else:
ambiente_str = self.ambiente[0]
# Se l'animale ha un verso, lo aggiunge alla stringa
if self.verso:
verso_str = " facendo un " + str(self.verso)
else:
verso_str = ""
# Se l'animale non ha zampe (=0) -> "è senza zampe", altrimenti "ha x zampe"
if self.numero_zampe == 0:
zampe_str = " è senza zampe, "
else:
zampe_str = " ha " + str(self.numero_zampe) + " zampe, "
# Specifica se l'animale ha sangue caldo o freddo
if self.sangue_caldo:
sangue_str = "sangue caldo"
else:
sangue_str = "sangue freddo"
# Restituisce la stringa finale dell'animale
return self.nome + zampe_str + sangue_str + " e vive in " + ambiente_str + verso_str
# Metodo __eq__: uguaglianza profonda fra animali - per confrontare due oggetti Animale
def __eq__(self, altro_animale):
# Controlla se altro_animale è un'istanza della classe Animale (o di sue sottoclassi)
if not isinstance(altro_animale, Animale):
return False
# Confronta i valori di tutti gli attributi
return (self.nome == altro_animale.get_nome() and self.sangue_caldo == altro_animale.get_sangue_caldo()
and self.numero_zampe == altro_animale.get_numero_zampe() and self.verso == altro_animale.get_verso()
and self.ambiente == altro_animale.get_ambiente())
# ######Classi delle classi animali########
"""
Tutte devono avere:
- un costruttore che inizializzi gli attributi ereditati e controlli i valori e tipi degli attributi aggiuntivi
- getter e setter per gli attributi aggiuntivi (controllando tipi e valori nei setter)
- __eq__ per l'uguaglianza profonda che dovranno sfruttare l'uguaglianza profonda della classe madre e controllare anche
gli attributi aggiuntivi
"""
# Funzione ausiliaria
# Controlla che settimane_gestazione sia un intero positivo
def parametro_settimane_gestazione(settimane_gestazione):
if not isinstance(settimane_gestazione, int):
raise TypeError("Il parametro 'settimane_gestazione' deve essere un intero.")
if settimane_gestazione <= 0:
raise ValueError("Il parametro 'settimane_gestazione' deve essere un intero positivo.")
class Mammifero(Animale):
"""
Classe Mammifero
ESTENDE Animale
STATO AGGIUNTIVO: settimane_gestazione: intero positivo
ALTRI METODI:
- __str__: esempio "(Mammifero) Tizio ha 4 zampe, sangue caldo e vive in Terra facendo un Ruggito, con 9 settimane
di gestazione"
"""
def __init__(self, nome, sangue_caldo, numero_zampe, verso, ambiente, settimane_gestazione):
# Invoca il costruttore della classe madre (Animale) per gestire gli attributi base
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente)
# Controlla che settimane_gestazione sia un intero positivo
parametro_settimane_gestazione(settimane_gestazione)
# Se i controlli passano, assegna il valore all'attributo settimane_gestazione
self.settimane_gestazione = settimane_gestazione
# Getter per settimane_gestazione
def get_settimane_gestazione(self):
return self.settimane_gestazione
# Setter per settimane_gestazione, verifica che il valore sia valido, se valido, aggiorna l'attributo
def set_settimane_gestazione(self, settimane_gestazione):
parametro_settimane_gestazione(settimane_gestazione)
self.settimane_gestazione = settimane_gestazione
# Ridefinisce __str__ per aggiungere la stringa "(Mammifero)" e settimane_gestazione
def __str__(self):
# Aggiunge la rappresentazione della classe madre e specifica le settimane di gestazione
return ("(Mammifero) " + super().__str__() + ", con " + str(self.settimane_gestazione) +
" settimane di gestazione")
# Ridefinisce __eq__ per confrontare gli attributi base e settimane_gestazione
def __eq__(self, altro_animale):
# Verifica che altro_animale sia un Mammifero
if not isinstance(altro_animale, Mammifero):
return False
# Confronta gli attributi base usando super().__eq__ e settimane_gestazione
return (super().__eq__(altro_animale) and
self.settimane_gestazione == altro_animale.get_settimane_gestazione())
# Funzione ausiliaria che controlla che volatile sia un booleano
def parametro_volatile(volatile):
if not isinstance(volatile, bool):
raise TypeError("Il parametro 'volatile' deve essere un booleano")
class Uccello(Animale):
"""
Classe Uccello
ESTENDE Animale
STATO AGGIUNTIVO: volatile: booleano
ALTRI METODI:
- __str__: esempio "(Uccello) Tizio ha 2 zampe, sangue caldo e vive in Terra facendo un Cinguettio, non sa volare"
"""
def __init__(self, nome, sangue_caldo, numero_zampe, verso, ambiente, volatile):
# Invoca il costruttore della superclasse Animale
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente)
# Controlla che volatile sia un booleano
parametro_volatile(volatile)
# Assegna il valore all'attributo volatile
self.volatile = volatile
# Getter per volatile
def get_volatile(self):
return self.volatile
# Setter per l'attributo volatile - controlla che sia un bool e aggiorna il suo valore
def set_volatile(self, volatile):
parametro_volatile(volatile)
self.volatile = volatile
# Ridefinisce __str__ per aggiungere la stringa "(Uccello)" e volo_str (sa/non sa volare)
def __str__(self):
if self.volatile:
volo_str = "sa volare"
else:
volo_str = "non sa volare"
return "(Uccello) " + super().__str__() + ", " + volo_str
# Ridefinisce __eq__ per confrontare gli attributi base e volatile
def __eq__(self, altro_animale):
# Controlla che l'altro animale sia un Uccello
if not isinstance(altro_animale, Uccello):
return False
# Confronta gli attributi base usando super().__eq__ e in più volatile
return (super().__eq__(altro_animale) and
self.volatile == altro_animale.get_volatile())
# Funzione ausiliaria che controlla che acqua sia una stringa fra "Dolce" e "Salata"
def parametro_acqua(acqua):
if not isinstance(acqua, str):
raise TypeError("Il parametro 'acqua' deve essere una stringa")
if acqua not in ["Dolce", "Salata"]:
raise ValueError("Il parametro 'acqua' deve essere una stringa fra 'Dolce' e 'Salata'")
class Pesce(Animale):
"""
Classe Pesce
ESTENDE Animale
STATO AGGIUNTIVO: acqua: stringa fra "Dolce" e "Salata"
ALTRI METODI:
- __str__: esempio "(Pesce) Tizio è senza zampe, sangue freddo e vive in Acqua Salata"
"""
def __init__(self, nome, sangue_caldo, numero_zampe, verso, ambiente, acqua):
# Invoca il costruttore della superclasse Animale
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente)
# Controlla che acqua sia una stringa fra "Dolce" e "Salata"
parametro_acqua(acqua)
# Assegna l'attributo acqua
self.acqua = acqua
# getter per acqua
def get_acqua(self):
return self.acqua
# setter per acqua - controlla che il parametro acqua sia valido, se lo è, aggiorna il suo valore
def set_acqua(self, acqua):
parametro_acqua(acqua)
self.acqua = acqua
# Ridefinisce __str__ per aggiungere "(Pesce)" e specificare il tipo di acqua
def __str__(self):
return "(Pesce) " + super().__str__() + " " + self.acqua
# Ridefinisce __eq__ per confrontare agli attributi base e acqua
def __eq__(self, altro_animale):
if not isinstance(altro_animale, Pesce):
return False
return (super().__eq__(altro_animale) and
self.acqua == altro_animale.get_acqua())
# ######Classi delle specie animali########
"""
Le classi da definire dovranno essere almeno:
- (estendendo Mammifero) Leone, Giraffa, Ippopotamo.
- (estendendo Uccello) Pinguino, Gufo.
- (estendendo Pesce) PescePagliaccio.
Tutte le classi devono avere:
- un costruttore che inizializzi gli attributi ereditati e controlli i valori e tipi degli attributi aggiuntivi
- __str__: in cui viene rappresentata la specie con la classe animale fra parentesi
Esempio 1: "Il Pinguino (Uccello) Kowalski ha 2 zampe, sangue freddo e vive in Terra e Acqua facendo un Garrito"
Esempio 2: "Il PescePagliaccio (Pesce) Nemo è senza zampe, sangue freddo e vive in Acqua Salata"
Esempio 3: "La Zebra (Mammifero) Marty ha 4 zampe, sangue caldo e vive in Terra facendo un Ruggito, con 56 settimane di
gestazione"
- file_str per la rappresentazione come stringa da scrivere su file. Ad esempio "Leone;Tizo;4;True;Terra;Ruggito;9"
- __eq__ per l'uguaglianza profonda
OPZIONALE: definire il costruttore per le classi delle specie animali che prende in input meno parametri, inizializzando
gli attributi ereditati con valori di default.
Esempio di uso:
Leone("Simba") inizializza il leone con i valori di default per gli attributi ereditati (True, 4, "Ruggito", ["Terra"],
7).
Però deve funzionare anche: Leone("Alex", True, 4, "Ruggito", ["Terra"], 7)
Attenzione che mettere nei costruttori Python valori di default come nelle altre funzioni crea problemi con la
mutabilità degli oggetti mutabili (come le liste degli ambienti).
Il consiglio è di usare None come valore di default e fare un check per inizializzare gli attributi con valori di
default in caso di None.
Ovviamente i test del main devono funzionare anche con questa modifica e i controlli di tipo e di valore devono essere
mantenuti.
"""
class Leone(Mammifero):
"""
Classe Leone, estende Mammifero
Default:
- sangue_caldo=True
- numero_zampe=4
- verso="Ruggito"
- ambiente=["Terra"]
- settimane_gestazione=7
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None,
settimane_gestazione=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = True
if numero_zampe is None:
numero_zampe = 4
if verso is None:
verso = "Ruggito"
if ambiente is None:
ambiente = ["Terra"]
if settimane_gestazione is None:
settimane_gestazione = 7
# Invoca il costruttore della superclasse Mammifero
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, settimane_gestazione)
self.specie = "Leone" # Specifica la specie come Leone
# Ridefinisce __str__ per restituire "Il Leone " e poi la stringa base di Mammifero
def __str__(self):
return "Il Leone " + super().__str__()
# Metodo file_str per la serializzazione su file
def file_str(self):
# Restituisce una stringa con i dati principali separati da ";"
return ("Leone;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
str(self.ambiente) + ";" + self.verso + ";" + str(self.settimane_gestazione))
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia un Leone e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, Leone) and super().__eq__(altro)
class Giraffa(Mammifero):
"""
Classe Giraffa, estende Mammifero
Default:
- sangue_caldo=True
- numero_zampe=4
- verso="Mugugno"
- ambiente=["Terra"]
- settimane_gestazione=65
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None,
settimane_gestazione=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = True
if numero_zampe is None:
numero_zampe = 4
if verso is None:
verso = "Mugugno"
if ambiente is None:
ambiente = ["Terra"]
if settimane_gestazione is None:
settimane_gestazione = 65
# Invoca il costruttore della superclasse Mammifero
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, settimane_gestazione)
self.specie = "Giraffa" # Specifica la specie come Giraffa
# Ridefinisce __str__ per restituire "La Giraffa " e poi la stringa base di Mammifero
def __str__(self):
return "La Giraffa " + super().__str__()
def file_str(self):
return ("Giraffa;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
str(self.ambiente) + ";" + self.verso + ";" + str(self.settimane_gestazione))
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia una Giraffa e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, Giraffa) and super().__eq__(altro)
class Ippopotamo(Mammifero):
"""
Classe Ippopotamo, estende Mammifero
Default:
- sangue_caldo=True
- numero_zampe=4
- verso="Ruggito"
- ambiente=["Terra", "Acqua"]
- settimane_gestazione=34
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None,
settimane_gestazione=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = True
if numero_zampe is None:
numero_zampe = 4
if verso is None:
verso = "Ruggito"
if ambiente is None:
ambiente = ["Terra", "Acqua"]
if settimane_gestazione is None:
settimane_gestazione = 34
# Invoca il costruttore della superclasse Mammifero
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, settimane_gestazione)
self.specie = "Ippopotamo" # Specifica la specie come Ippopotamo
# Rappresentazione come stringa, aggiunge "L'Ippopotamo " all'inizio della stringa di Mammifero
def __str__(self):
return "L'Ippopotamo " + super().__str__()
# Uso " e ".join(self.ambiente) per gestire il caso in cui ci sono due ambienti
def file_str(self):
return ("Ippopotamo;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
" e ".join(self.ambiente) + ";" + self.verso + ";" + str(self.settimane_gestazione))
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia un Ippopotamo e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, Ippopotamo) and super().__eq__(altro)
class Pinguino(Uccello):
"""
Classe Pinguino, estende Uccello
Default:
- sangue_caldo=True
- numero_zampe=2
- verso="Garrito"
- ambiente=["Terra", "Acqua"]
- volatile=False
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None, volatile=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = True
if numero_zampe is None:
numero_zampe = 2
if verso is None:
verso = "Garrito"
if ambiente is None:
ambiente = ["Terra", "Acqua"]
if volatile is None:
volatile = False
# Invoca il costruttore della superclasse Uccello
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, volatile)
self.specie = "Pinguino" # Specifica la specie come Pinguino
# Aggiunge "Il Pinguino " all'inizio
def __str__(self):
return "Il Pinguino " + super().__str__()
def file_str(self):
return ("Pinguino;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
" e ".join(self.ambiente) + ";" + self.verso + ";" + str(self.volatile))
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia un Pinguino e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, Pinguino) and super().__eq__(altro)
class Gufo(Uccello):
"""
Classe Gufo, estende Uccello
Default:
- sangue_caldo=True
- numero_zampe=2
- verso="Bubbolio"
- ambiente=["Aria"]
- volatile=True
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None, volatile=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = True
if numero_zampe is None:
numero_zampe = 2
if verso is None:
verso = "Bubbolio"
if ambiente is None:
ambiente = ["Aria"]
if volatile is None:
volatile = True
# Invoca il costruttore della superclasse Uccello
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, volatile)
self.specie = "Gufo" # Specifica la specie come Gufo
# Aggiunge "Il Gufo " all'inizio
def __str__(self):
return "Il Gufo " + super().__str__()
def file_str(self):
return ("Gufo;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
str(self.ambiente) + ";" + self.verso + ";" + str(self.volatile))
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia un Gufo e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, Gufo) and super().__eq__(altro)
class PescePagliaccio(Pesce):
"""
Classe PescePagliaccio, estende Pesce
Default:
- sangue_caldo=False
- numero_zampe=0
- verso=""
- ambiente=["Acqua"]
- acqua="Salata"
"""
def __init__(self, nome, sangue_caldo=None, numero_zampe=None, verso=None, ambiente=None, acqua=None):
# Se i parametri non sono passati, assegna i valori di default
if sangue_caldo is None:
sangue_caldo = False
if numero_zampe is None:
numero_zampe = 0
if verso is None:
verso = ""
if ambiente is None:
ambiente = ["Acqua"]
if acqua is None:
acqua = "Salata"
# Invoca il costruttore della superclasse Pesce
super().__init__(nome, sangue_caldo, numero_zampe, verso, ambiente, acqua)
self.specie = "PescePagliaccio" # Specifica la specie come PescePagliaccio
# Aggiunge "Il PescePagliaccio " all'inizio della stringa di base
def __str__(self):
return "Il PescePagliaccio " + super().__str__()
def file_str(self):
return ("PescePagliaccio;" + self.nome + ";" + str(self.numero_zampe) + ";" + str(self.sangue_caldo) + ";" +
str(self.ambiente) + ";" + self.verso + ";" + self.acqua)
# Ridefinisce __eq__ per verificare che l'altro oggetto (altro) sia un PescePagliaccio e abbia gli stessi attributi
def __eq__(self, altro):
return isinstance(altro, PescePagliaccio) and super().__eq__(altro)
gui.py
Codice completo
from zoo import *
from animali import *
import tkinter as tk
from tkinter import messagebox, filedialog
"""
La GUI dovrà essere implementata in un oggetto che crea la finestra principale e gestisce gli eventi.
L'aspetto della GUI è libero, deve gestire opportunamente gli errori e fornire un feedback all'utente.
Devono essere mostrati tutti gli animali presenti nello zoo divisi per zona, mostrando almeno nome e specie.
Le operazioni minime da implementare sono:
- inserire un nuovo animale nello zoo,
- modificare la zona di un animale,
- mostrare la stringa di un animale dato il nome,
- ottenere la lista dei nomi degli animali appartenenti a una specie,
- ottenere la lista degli animali che vivono in un determinato ambiente,
- ottenere il numero di animali data una classe,
- caricare lo zoo da file,
- salvare lo zoo su file,
- uscire dall'applicazione.
Per operazioni non intuitive (es: inserimento dei dati di una animale) fornire un aiuto all'utente tramite istruzioni.
"""
class MyApp:
def __init__(self, root):
self.root = root
self.root.title("Gestione Zoo")
# Inizializzo i riferimenti ai campi di input come None per dichiarare esplicitamente questi attributi all'IDE
# In create_label_and_entry(), verranno riassegnati ai rispettivi tk.Entry
self.entry_nome = None
self.entry_specie = None
self.entry_classe = None
self.entry_ambiente = None
self.entry_zona = None
# Crea un'istanza della classe Zoo che si trova in zoo.py
self.zoo = Zoo()
# Creo il layout generale della finestra con tre frame principali - top, centrale, bottom
# Creo un frame nella parte superiore della finestra per i pulsanti "Esci" e "?"
self.frameTop = tk.Frame(root)
self.frameTop.pack(side=tk.TOP, fill=tk.BOTH, padx=5, pady=5)
# frame_centrale: contiene i campi di input e i pulsanti principali
self.frame_centrale = tk.Frame(root)
self.frame_centrale.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# frame_bottom: contiene l'area di visualizzazione (text area) e i pulsanti di filtraggio
self.frame_bottom = tk.Frame(root)
self.frame_bottom.pack(side=tk.BOTTOM, expand=True, padx=5, pady=5)
# Aggiungo i pulsanti nel frameTop
# Creo il pulsante "Esci" (chiude l'app)
self.esci_btn = tk.Button(self.frameTop, text="Esci", bg="salmon", relief="raised")
self.esci_btn.pack(side=tk.RIGHT, padx=10, pady=10)
# Collego il pulsante al metodo root.quit() che interrompe il mainloop
self.esci_btn.bind("<Button-1>", lambda event: root.quit())
# Creo un pulsante "?" per mostrare aiuto
self.aiuto_btn = tk.Button(self.frameTop, text="?", bg="violet", relief="raised")
self.aiuto_btn.pack(side=tk.LEFT, padx=10, pady=10)
# Collego il pulsante alla funzione `mostra_aiuto` che apre un messagebox con istruzioni
self.aiuto_btn.bind("<Button-1>", self.mostra_aiuto)
# Aggiungo i campi di input nel frame centrale
# Creo un frame per posizionare i campi di input
self.input_frame = tk.Frame(self.frame_centrale)
self.input_frame.pack(side=tk.LEFT, expand=True, padx=20, pady=20)
# Creo etichette e campi di input per inserire i dettagli degli animali
self.create_label_and_entry("Nome:", 0)
self.create_label_and_entry("Specie (es. Leone):", 1)
self.create_label_and_entry("Classe (es. Mammifero):", 2)
self.create_label_and_entry("Ambiente (es. Terra):", 3)
self.create_label_and_entry("Zona (es. A1):", 4)
# Aggiungo i pulsanti principali per gestire gli animali
# Creo un frame per i pulsanti principali
self.pulsanti_principali = tk.Frame(self.frame_centrale)
self.pulsanti_principali.pack(side=tk.TOP, expand=True, padx=10, pady=10)
# Pulsante "Inserisci Animale"
self.inserisci_btn = tk.Button(self.pulsanti_principali, text="Inserisci Animale", bg="lightgreen",
relief="raised")
# Posiziono il pulsante in una griglia
self.inserisci_btn.grid(row=0, column=0, padx=10, pady=10)
self.inserisci_btn.bind("<Button-1>", self.inserisci_animale)
# Pulsante "Cambia Zona"
self.cambia_zona_btn = tk.Button(self.pulsanti_principali, text="Cambia Zona", bg="lightgreen", relief="raised")
# Lo posiziono accanto al precedente
self.cambia_zona_btn.grid(row=0, column=1, padx=10, pady=10)
self.cambia_zona_btn.bind("<Button-1>", self.modifica_zona)
# Pulsante "Mostra Animali"
self.mostra_animali_btn = tk.Button(self.pulsanti_principali, text="Mostra Animali", bg="gold", relief="raised")
# Posiziona il pulsante su una nuova riga
self.mostra_animali_btn.grid(row=1, column=0, columnspan=2, padx=5, pady=5)
self.mostra_animali_btn.bind("<Button-1>", self.mostra_animali)
# Pulsante "Salva Zoo"
self.salva_btn = tk.Button(self.pulsanti_principali, text="Salva Zoo", bg="lightcoral", relief="raised")
# Posiziona il pulsante in una nuova riga
self.salva_btn.grid(row=2, column=0, columnspan=2, padx=5, pady=5)
self.salva_btn.bind("<Button-1>", self.salva_zoo)
# Pulsante "Carica Zoo"
self.carica_btn = tk.Button(self.pulsanti_principali, text="Carica Zoo", bg="lightcoral", relief="raised")
# Posiziona il pulsante in una nuova riga
self.carica_btn.grid(row=3, column=0, columnspan=2, padx=5, pady=5)
self.carica_btn.bind("<Button-1>", self.carica_zoo)
# Aggiungo un'etichetta per mostrare messaggi di successo
# Creo un'etichetta vuota che sarà usata per mostrare messaggi di successo
# - usata per la scritta del cambio zona con successo
self.success_label = tk.Label(self.frame_centrale, text="", fg="green")
self.success_label.pack(pady=10)
# Aggiungo una text area nella parte inferiore per mostrare gli animali
# Creo una text area per visualizzare gli animali
self.text_area = tk.Text(self.frame_bottom, height=10, width=50, bg="#FFEB99", relief="raised")
self.text_area.pack(side=tk.LEFT, padx=10, pady=10)
# Aggiungo i pulsanti per le funzioni, che io ho chiamato di filtraggio degli animali (mostra per...),
# nella parte inferiore
# Creo un frame per i pulsanti di filtraggio
self.frame_funzioni_filtraggio = tk.Frame(self.frame_bottom)
self.frame_funzioni_filtraggio.pack(side=tk.RIGHT, padx=20)
self.mostra_animale_per_nome_btn = tk.Button(self.frame_funzioni_filtraggio, text="Mostra Animale per Nome",
bg="lightblue", relief="raised")
self.mostra_animale_per_nome_btn.grid(row=0, column=0, pady=5)
self.mostra_animale_per_nome_btn.bind("<Button-1>", self.mostra_animale_per_nome)
self.mostra_animali_per_specie_btn = tk.Button(self.frame_funzioni_filtraggio, text="Mostra Animali per Specie",
bg="lightblue", relief="raised")
self.mostra_animali_per_specie_btn.grid(row=1, column=0, pady=5)
self.mostra_animali_per_specie_btn.bind("<Button-1>", self.mostra_animali_per_specie)
self.mostra_animali_per_ambiente_btn = tk.Button(self.frame_funzioni_filtraggio, text="Mostra Animali "
"per Ambiente",
bg="lightblue", relief="raised")
self.mostra_animali_per_ambiente_btn.grid(row=2, column=0, padx=5, pady=5)
self.mostra_animali_per_ambiente_btn.bind("<Button-1>", self.mostra_animali_per_ambiente)
self.mostra_numero_animali_per_classe_btn = tk.Button(self.frame_funzioni_filtraggio, text="Mostra "
"Numero Animali "
"per Classe",
bg="lightblue", relief="raised")
self.mostra_numero_animali_per_classe_btn.grid(row=3, column=0, padx=5, pady=5)
self.mostra_numero_animali_per_classe_btn.bind("<Button-1>", self.mostra_numero_animali_per_classe)
def create_label_and_entry(self, text, row):
"""
Crea un'etichetta e un campo di testo (Entry) in una riga specifica e li posiziona nella griglia self.input_frame.
:param self: riferimento all'istanza della classe MyApp
:param text: testo dell'etichetta (es. "Nome:", "Specie:") - stringa
:param row: indice di riga nella griglia - int
"""
label = tk.Label(self.input_frame, text=text)
label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
entry = tk.Entry(self.input_frame)
entry.grid(row=row, column=1, padx=5, pady=5)
# Salvo l'oggetto 'entry' in un attributo self.entry_ a seconda del testo
if "Nome" in text:
self.entry_nome = entry
elif "Specie" in text:
self.entry_specie = entry
elif "Classe" in text:
self.entry_classe = entry
elif "Ambiente" in text:
self.entry_ambiente = entry
elif "Zona" in text:
self.entry_zona = entry
# Recupera i dati dai campi di input per creare l'animale e lo inserisce nello zoo
def inserisci_animale(self, event):
# Raccoglie i dati - strip() rimuove spazi eventuali in eccesso
nome = self.entry_nome.get().strip()
specie = self.entry_specie.get().strip()
classe = self.entry_classe.get().strip()
ambiente = self.entry_ambiente.get().strip()
zona = self.entry_zona.get().strip()
# Validazione dei dati inseriti nei campi di input
if not nome:
messagebox.showerror("Errore", "Il nome non può essere vuoto.")
return
if not specie:
messagebox.showerror("Errore",
"Specie non può essere vuota.nSuggerimento: nInserisci una specie valida "
"tra queste:Leone, Giraffa, Pinguino, Ippopotamo, PescePagliaccio, Gufo")
return
if specie not in ["Leone", "Giraffa", "Pinguino", "Ippopotamo", "PescePagliaccio", "Gufo"]:
messagebox.showerror("Errore", "Specie non riconosciuta!nSuggerimento: nInserisci una specie "
"valida tra queste:Leone, Giraffa, Pinguino, Ippopotamo, "
"PescePagliaccio, Gufo")
return
if not classe:
messagebox.showerror("Errore",
"Classe non può essere vuota.nSuggerimento: nInserisci una classe valida"
" tra queste: Mammifero, Uccello, Pesce.")
return
if classe not in ["Mammifero", "Uccello", "Pesce"]:
messagebox.showerror("Errore", "Classe non riconosciuta!nSuggerimento: nInserisci una classe "
"valida tra queste: Mammifero, Uccello, Pesce.")
return
if not ambiente:
messagebox.showerror("Errore",
"Ambiente non può essere vuoto.nSuggerimento: nInserisci un ambiente valido "
"tra: Terra, Acqua, Aria. nSe sono due ambienti, separali da una virgola. "
"Es. Terra, Acqua")
return
# Ambienti multipli separati da virgola
ambiente = [a.strip() for a in ambiente.split(",")]
# Validazione degli ambienti multipli
if not all(a in ["Terra", "Acqua", "Aria"] for a in ambiente):
messagebox.showerror("Errore", "Ambiente non riconosciuto!nSuggerimento: nInserisci un "
"ambiente valido tra: Terra, Acqua, Aria. nSe sono due ambienti, "
"separali da una virgola. Es. Terra, Acqua")
return
if not zona:
messagebox.showerror("Errore", "Zona non può essere vuota.nSuggerimento: nDeve essere "
"composta da una lettera maiuscola seguita da numeri.nEs. A1, B2.")
return
if not zona[0].isupper() or not zona[1:].isdigit():
messagebox.showerror("Errore",
"Zona non valida!nDeve essere composta da una lettera maiuscola "
"seguita da numeri.nEs. A1, B2.")
return
# Se i check passano, creo l'animale con parametri di default
try:
if specie == "Leone":
animale = Leone(nome, True, 4, "Ruggito", ambiente, 7)
elif specie == "Giraffa":
animale = Giraffa(nome, True, 4, "Mugugno", ambiente, 65)
elif specie == "Pinguino":
animale = Pinguino(nome, True, 2, "Garrito", ambiente, False)
elif specie == "Ippopotamo":
animale = Ippopotamo(nome, True, 4, "Ruggito", ambiente, 34)
elif specie == "PescePagliaccio":
animale = PescePagliaccio(nome, False, 0, "", ambiente, "Salata")
elif specie == "Gufo":
animale = Gufo(nome, True, 2, "Bubbolio", ambiente, True)
else:
raise ValueError("Specie non valida.")
# Inserisco l'animale nello zoo
self.zoo.inserisci(animale, zona)
# Mostro un messaggio di successo se l'animale è stato inserito
messagebox.showinfo("Successo", "Animale '" + nome + "' inserito con successo nella zona " + zona + "!")
# Pulisco i campi di input
self.clear_entries()
except KeyError:
# Errore se un animale con lo stesso nome esiste già
root.after(250,
lambda: messagebox.showerror("Errore", "Un animale con il nome '" + nome +
"' esiste già nello zoo."))
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore di validazione", str(error)))
# Modifica la zona di un animale esistente
def modifica_zona(self, event):
# Raccoglie il nome e la nuova zona dai campi di input
nome = self.entry_nome.get()
zona = self.entry_zona.get()
if not nome or not zona:
# Controlla se il nome e la zona sono stati compilati
messagebox.showerror("Errore", "Nome e zona devono essere compilati.")
return
try:
# Modifico la zona dell'animale
self.zoo.cambia_zona(nome, zona)
# Aggiorno il messaggio di successo
self.success_label.config(text="Zona modificata con successo!")
# Aggiorno la visualizzazione degli animali
self.mostra_animali(event)
except KeyError:
root.after(250, lambda: messagebox.showerror("Errore", "Animale non trovato nello zoo."))
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore di validazione", str(error)))
# Mostra tutti gli animali suddivisi per zona, scrivendo i risultati dentro la text_area
# Ottiene i dati da self.zoo.zone_animali()
def mostra_animali(self, event):
# Cancello il contenuto della text area
self.text_area.delete(1.0, tk.END)
animali_per_zona = self.zoo.zone_animali()
for zona, animali in sorted(animali_per_zona.items()):
# Scrive la zona
self.text_area.insert(tk.END, "Zona " + zona + ":n")
for nome in animali:
# Ottiene l'oggetto animale per il nome e lo scrive
animale = self.zoo.animale(nome)
self.text_area.insert(tk.END, "- " + nome + " (" + animale.specie + ")n")
self.text_area.insert(tk.END, "n")
# Mostra i dettagli di un animale dato il suo nome con str(animale)
def mostra_animale_per_nome(self, event):
# Raccoglie il nome dall'input
nome = self.entry_nome.get()
if not nome:
messagebox.showerror("Errore", "Nome Animale non può essere vuoto.")
return
try:
# Recupero l'animale dallo zoo
animale = self.zoo.animale(nome)
# Cancello il contenuto della text area
self.text_area.delete(1.0, tk.END)
# Mostro i dettagli dell'animale - l'animale come stringa
self.text_area.insert(tk.END, "Animale trovato: " + str(animale) + "n")
except KeyError:
root.after(250, lambda: messagebox.showerror("Errore", "Animale non trovato nello zoo."))
# Mostra tutti gli animali della specie indicata (self.entry_specie)
def mostra_animali_per_specie(self, event):
# Raccoglie la specie dall'input
specie = self.entry_specie.get()
if not specie:
messagebox.showerror("Errore", "Specie non può essere vuoto.")
return
try:
# Recupero la lista degli animali appartenenti alla specie dallo zoo
animali_specie = self.zoo.animali_specie(specie)
# Cancello il contenuto della text area
self.text_area.delete(1.0, tk.END)
if not animali_specie:
self.text_area.insert(tk.END, "Nessun animale trovato per questa specie.")
else:
# Mostro gli animali della specie nella text area
self.text_area.insert(tk.END, "Animali della specie " + specie + ":n")
for nome in animali_specie:
self.text_area.insert(tk.END, "- " + nome + "n")
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore di inserimento "
"per la ricerca: n" + str(error)))
# Mostra la lista degli animali che vivono in un determinato ambiente
def mostra_animali_per_ambiente(self, event):
# Raccoglie l'ambiente dall'input
ambiente = self.entry_ambiente.get()
if not ambiente:
messagebox.showerror("Errore", "Ambiente non può essere vuoto.")
return
try:
# Recupero la lista degli animali per l'ambiente specificato
animali_ambiente = self.zoo.animali_ambiente(ambiente)
# Cancello il contenuto della text area
self.text_area.delete(1.0, tk.END)
if not animali_ambiente:
self.text_area.insert(tk.END, "Nessun animale trovato per questo ambiente.")
else:
# Mostro gli animali che vivono nell'ambiente specificato
self.text_area.insert(tk.END, "Animali che vivono in " + ambiente + ":n")
for nome in animali_ambiente:
self.text_area.insert(tk.END, "- " + nome + "n")
except ValueError as e:
# Gestisce errori di validazione
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore di inserimento per"
" la ricerca: n" + str(error)))
# Mostra il numero di animali appartenenti a una specifica classe
def mostra_numero_animali_per_classe(self, event=None):
# Raccoglie la classe dall'input
classe = self.entry_classe.get()
if not classe:
messagebox.showerror("Errore", "Classe non può essere vuota.")
return
try:
# Conto il numero di animali nella classe specificata
numero_animali = self.zoo.conta_classe(classe)
# Cancello il contenuto della text area
self.text_area.delete(1.0, tk.END)
# Mostro il numero di animali nella classe
self.text_area.insert(tk.END, "Numero di animali della classe " + classe + ": " + str(numero_animali))
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore di inserimento per "
"la ricerca: n" + str(error)))
# Salva lo stato attuale dello zoo in un file
def salva_zoo(self, event):
# Apre una finestra di dialogo per scegliere il file in cui salvare
try:
file_name = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files", "*.txt")])
# Se l'utente annulla, esce dalla funzione
if not file_name:
return
# Salvo lo zoo nel file specificato
self.zoo.salva(file_name)
# Mostro un messaggio di successo
messagebox.showinfo("Successo", "Zoo salvato in " + file_name)
except IOError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore durante il salvataggio:"
" n" + str(error)))
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore nei dati: n" + str(error)))
# Carica lo stato dello zoo da un file
def carica_zoo(self, event):
try:
# Apre una finestra di dialogo per scegliere il file da cui caricare
file_name = filedialog.askopenfilename(filetypes=[("Text files", "*.txt")])
# Se l'utente annulla, esce dalla funzione
if not file_name:
return
self.zoo.carica(file_name)
messagebox.showinfo("Successo", "Zoo caricato da " + file_name)
except IOError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore durante il caricamento: "
"n" + str(error)))
except ValueError as e:
root.after(250, lambda error=e: messagebox.showerror("Errore", "Errore nei dati: n" + str(error)))
# Mostra un messagebox con informazioni su come usare l'app
def mostra_aiuto(self, event):
messagebox.showinfo("Aiuto", "Inserisci tutti i dati dell'animale.nn"
"Usa i pulsanti per inserire, modificare e visualizzare gli animali.n"
"Puoi anche caricare e salvare lo zoo da un file txt."
"nnSuggerimento per la compilazione dei campi: nnInserisci un nome. "
"nnInserisci una specie valida tra queste:"
"Leone, Giraffa, Pinguino, Ippopotamo, PescePagliaccio, Gufo nnInserisci una "
"classe valida tra queste: Mammifero, Uccello, Pesce nnInserisci un ambiente "
"valido tra: Terra, Acqua, Aria. nSe sono due ambienti, separali da una virgola."
" nEs. Terra, Acqua nnZona deve essere composta da una lettera maiuscola "
"seguita da numeri.nEs. A1, B2.")
# Cancella tutto il testo dei campi di input - indice 0 (inizio testo) e tk.END (fine testo)
def clear_entries(self):
self.entry_nome.delete(0, tk.END)
self.entry_specie.delete(0, tk.END)
self.entry_classe.delete(0, tk.END)
self.entry_ambiente.delete(0, tk.END)
self.entry_zona.delete(0, tk.END)
# Avvia l'applicazione
root = tk.Tk()
app = MyApp(root)
root.mainloop()

Inizia a esplorare il progetto ora.
Approfondisci le funzionalità del Gestionale Zoo.
