02 - Programmazione a oggetti e Argomenti avanzati

Programmazione Orientata agli Oggetti

La Programmazione Orientata agli Oggetti (OOP) è un paradigma di programmazione che utilizza "oggetti" e "classi" per strutturare il codice. È un modo di organizzare il software che cerca di modellare il mondo reale in termini di entità con attributi e comportamenti. In Python, questo paradigma è ampiamente utilizzato per rendere il codice più modularizzato, riutilizzabile e manutenibile.

Concetti di Base

Introduzione alla Programmazione Orientata agli Oggetti

La OOP si basa su quattro principi fondamentali:

  1. Incapsulamento
  2. Ereditarietà
  3. Polimorfismo
  4. Astrazione

Incapsulamento implica la limitazione dell'accesso agli attributi e ai metodi di un oggetto, fornendo solo un'interfaccia pubblica per interagire con esso. Ereditarietà permette di creare nuove classi basate su classi esistenti. Polimorfismo consente di trattare oggetti di diversi tipi in modo uniforme. Astrazione consiste nel nascondere i dettagli complessi e mostrare solo le caratteristiche essenziali.

Classi e Oggetti

Classe: È un modello (o blueprint) che definisce la struttura e il comportamento degli oggetti. Una classe specifica cosa un oggetto "è" e cosa può "fare".

Oggetto: È un'istanza di una classe. È una rappresentazione concreta di qualcosa basato sulla classe, con dati e comportamento definiti.

Definizione e Istanziazione delle Classi

Proprietà e Metodi

Le proprietà sono attributi o dati che gli oggetti possiedono, mentre i metodi sono funzioni definite all'interno di una classe che descrivono i comportamenti degli oggetti.

Definizione di Proprietà

Definizione di Metodi


Ereditarietà

L'ereditarietà permette di creare una nuova classe basata su un'altra classe esistente. La nuova classe (sottoclasse) eredita attributi e metodi dalla classe esistente (superclasse).

Creazione di Classi Derivate

Metodi Override e super()


Oggetti e Costruttori

Il costruttore è un metodo speciale (__init__) che viene eseguito automaticamente quando un oggetto viene creato, per inizializzare gli attributi dell'oggetto.

Costruttori (__init__)

Distruttori (__del__)

Il distruttore è un metodo speciale (__del__) che viene eseguito quando un oggetto viene distrutto. È usato per pulire le risorse.


Esempio Completo di Programmazione a Oggetti

Definizione delle classi:

class Persona:
    def __init__(self, nome, eta):
        self.nome = nome
        self.eta = eta

    def descrizione(self):
        return f"{self.nome} ha {self.eta} anni."

class Studente(Persona):
    def __init__(self, nome, eta, corso):
        super().__init__(nome, eta)
       

 self.corso = corso

    def descrizione(self):
        return f"{self.nome} ha {self.eta} anni e studia {self.corso}."

Creazione e uso degli oggetti:

persona = Persona("Alice", 30)
print(persona.descrizione())
# Output: Alice ha 30 anni.

studente = Studente("Bob", 20, "Informatica")
print(studente.descrizione())
# Output: Bob ha 20 anni e studia Informatica.

In questo esempio, Persona è la superclasse e Studente è la sottoclasse che estende Persona aggiungendo l'attributo corso e sovrascrivendo il metodo descrizione.


Argomenti Avanzati

List Comprehension

Sintassi e utilizzo delle list comprehension

Le list comprehension

offrono un modo conciso per creare liste.


Comprensione delle set e dei dizionari


Certamente! Ecco la sezione sulle funzioni lambda aggiornata con esempi reali, accompagnati dalle versioni equivalenti senza l'uso di lambda per un confronto chiaro.


Funzioni lambda

Le funzioni lambda sono funzioni anonime e brevi, definite utilizzando la parola chiave lambda. Sono utili per eseguire operazioni semplici che non richiedono una funzione nominata. Le lambda vengono interpretate come funzioni inline che possono essere passate come argomenti ad altre funzioni.

Definizione e utilizzo delle espressioni lambda


Utilizzo reale delle lambda

Le funzioni lambda sono utili in situazioni dove è richiesta una funzione breve e senza nome. Sono comunemente utilizzate con funzioni che accettano altre funzioni come argomenti.






Limiti delle funzioni lambda

In sintesi, le funzioni lambda in Python sono strumenti potenti per definire funzioni brevi e anonime utilizzabili in molti contesti funzionali, come l'ordinamento, il filtraggio e la trasformazione dei dati. Tuttavia, per funzioni più complesse, è meglio utilizzare funzioni nominate per una maggiore chiarezza e leggibilità del codice.

Decoratori

I decoratori sono una forma di closure che permettono di estendere il comportamento delle funzioni.


Gestione Avanzata delle Eccezioni

Eccezioni Predefinite

Gerarchia delle eccezioni in Python

Le eccezioni in Python derivano dalla classe base BaseException. Capire la gerarchia delle eccezioni aiuta a gestirle in modo più efficace.


Eccezioni comuni


Definire Eccezioni Personalizzate

Creazione di nuove classi di eccezioni

Puoi definire le tue eccezioni per gestire errori specifici.


Utilizzo delle eccezioni personalizzate

Le eccezioni personalizzate migliorano la leggibilità e la gestione degli errori nel codice.


Creazione di Moduli e Pacchetti

Scrivere e importare moduli personalizzati

Puoi creare i tuoi moduli per organizzare meglio il codice. Un modulo personalizzato è semplicemente un file Python (.py) che contiene definizioni di funzioni, variabili e classi.


Uso della variabile __name__

La variabile __name__ permette di controllare se il file viene eseguito direttamente o importato come modulo.


Creare pacchetti Python

I pacchetti sono collezioni di moduli organizzati in una struttura di directory. Ogni directory contiene un file __init__.py che può essere vuoto o contenere codice di inizializzazione per il pacchetto.


Esempi pratici

Modulo personalizzato:

Pacchetto personalizzato:


Esecuzione Diretta di Moduli e File __main__.py

In Python, i moduli possono essere eseguiti direttamente come script oppure importati da altri moduli. La gestione di questo comportamento è fondamentale per creare script riutilizzabili ed eseguibili.

Esecuzione Diretta di Moduli

Quando un modulo viene eseguito direttamente, la variabile speciale __name__ è impostata su "__main__". Questo permette di scrivere codice che viene eseguito solo se il modulo è eseguito come script principale, e non quando è importato come modulo.


File __main__.py

Il file __main__.py è un file speciale che viene eseguito quando un pacchetto viene eseguito come script. Questo è utile per creare pacchetti che possono essere eseguiti direttamente da linea di comando.

Struttura di un pacchetto con __main__.py

Supponiamo di avere una struttura di pacchetto come segue:

mio_pacchetto/
    __init__.py
    modulo1.py
    __main__.py

Differenze tra __main__.py e __init__.py

In sintesi, __main__.py è utilizzato per eseguire un pacchetto come script, mentre __init__.py è utilizzato per inizializzare il pacchetto quando viene importato. Entrambi i file svolgono ruoli specifici e complementari nella gestione e nell'uso dei pacchetti Python.