La programmazione orientata agli oggetti (OOP) è un concetto ampiamente usato per scrivere applicazioni. Come data scientist, dovresti essere in grado di scrivere applicazioni per elaborare i tuoi dati, tra le altre cose.
In questo tutorial, tratto le basi della programmazione orientata agli oggetti in Python. Imparerai come creare una classe e istanziarne oggetti, come definire metodi e passare loro argomenti, e come aggiungere attributi a una classe.
TL;DR
L’OOP organizza il codice attorno a classi (progetti) e oggetti (istanze di quei progetti)
Definisci una classe con la keyword
classe inizializza gli attributi dentro__init__(self)I metodi di istanza operano sui dati dell’oggetto tramite
selfe si chiamano con la dot notation:my_object.method()Python supporta tutti e quattro i pilastri dell’OOP: incapsulamento, ereditarietà, polimorfismo e astrazione
L’OOP favorisce il riuso del codice per creare più oggetti a partire da un unico template di classe
Che cos’è l’OOP?
La programmazione orientata agli oggetti (OOP) si basa sul paradigma di programmazione imperativa, che usa istruzioni per cambiare lo stato di un programma. Imperativa significa che si concentra sul descrivere come un programma dovrebbe operare. Esempi di linguaggi imperativi sono C, C++, Java, Go, Ruby e Python.
Questo è in contrasto con la programmazione dichiarativa, che si concentra su cosa il programma dovrebbe ottenere, senza specificare come (ad es. SQL, XQuery). Per un confronto tra paradigmi, vedi la nostra guida a programmazione funzionale vs OOP.
L’OOP usa il concetto di oggetti e classi. Una classe può essere vista come un “progetto” per gli oggetti. Questi possono avere i propri
- Attributi: caratteristiche che possiedono
- Metodi: azioni che eseguono
L’OOP ha alcuni vantaggi chiave rispetto ad altri design pattern. Lo sviluppo è spesso più veloce ed economico, e l’approccio modulare rende il software più facile da mantenere. Questo, a sua volta, porta a software di qualità superiore, che è anche estendibile con nuovi metodi e attributi.
La curva di apprendimento è però più lunga. I concetti fondamentali richiedono un investimento iniziale maggiore per essere compresi appieno. Dal punto di vista computazionale, il software OOP è più lento e usa più memoria, poiché bisogna scrivere più righe di codice.
I quattro pilastri dell’OOP
Oltre a classi e metodi, l’OOP poggia su quattro pilastri che ho solo accennato finora. Ognuno merita un approfondimento dedicato, ma ecco una rapida panoramica con link a tutorial specifici.
| Pilastro | Cosa significa | Per saperne di più |
|---|---|---|
| Incapsulamento | Raggruppare dati e metodi insieme, controllando l’accesso con convenzioni di naming (_private, __name_mangled) | Incapsulamento in Python |
| Ereditarietà | Creare classi figlie che riusano ed estendono il comportamento della classe genitore | Ereditarietà in Python |
| Polimorfismo | Classi diverse che condividono la stessa interfaccia, così il codice può lavorare con oggetti in modo intercambiabile | Corso OOP in Python |
| Astrazione | Nascondere la complessità dietro un’interfaccia pulita usando classi base astratte | Classi astratte in Python |
Esempio di OOP
Un esempio di classe è la classe Dog. Non pensarla come un cane specifico, o il tuo cane. Stiamo descrivendo che cosa un cane è e cosa può fare, in generale. I cani di solito hanno un name e un age; questi sono attributi di istanza. I cani possono anche bark; questo è un metodo.
Quando parli di un cane specifico, in programmazione avresti un oggetto: un oggetto è un’istanza di una classe. Questo è il principio di base su cui si fonda la programmazione orientata agli oggetti. Quindi il mio cane Ozzy, per esempio, appartiene alla classe Dog. I suoi attributi sono name = 'Ozzy' e age = '2'. Un cane diverso avrà attributi diversi.
OOP in Python
Python è uno dei linguaggi più popolari per imparare e applicare l’OOP. Qui sotto vediamo perché Python si presta bene al codice orientato agli oggetti e passiamo in rassegna la sintassi di base.
Python è orientato agli oggetti?
Python è un ottimo linguaggio di programmazione che supporta l’OOP. Lo userai per definire una classe con attributi e metodi, che poi chiamerai.
Il vantaggio chiave di Python rispetto ad altri linguaggi come Java, C++ o R è che è un linguaggio dinamico con tipi di dato di alto livello. Ciò significa che non richiede al programmatore di dichiarare i tipi di variabili e argomenti, il che rende lo sviluppo molto più rapido e il codice più leggibile rispetto a Java o C++.
Se sei alle prime armi con Python, dai un’occhiata al nostro corso Introduzione a Python.
Come creare una classe
Per definire una classe in Python, puoi usare la class keyword, seguita dal nome della classe e dai due punti.
All’interno della classe, di solito si definisce un metodo __init__ (un metodo dunder) con def. Questo è l’inizializzatore che potrai poi usare per istanziare oggetti. È simile a un costruttore in Java.
__init__ viene chiamato automaticamente quando crei un nuovo oggetto. Accetta un argomento: self, che si riferisce all’oggetto stesso.
All’interno del metodo, per ora si usa la keyword pass, perché Python si aspetta che tu scriva qualcosa lì. Ricorda di usare l’indentazione corretta!
class Dog:
def __init__(self):
passNota: self in Python è equivalente a this in C++ o Java.
In questo caso, hai una classe Dog (per lo più vuota), ma ancora nessun oggetto. Creiamone uno!
Istanziare oggetti
Per istanziare un oggetto, digita il nome della classe, seguito da due parentesi tonde. Puoi assegnarlo a una variabile per tenere traccia dell’oggetto.
ozzy = Dog()
E stampalo:
print(ozzy)
<__main__.Dog object at 0x111f47278>
Aggiungere attributi a una classe
Dopo aver stampato ozzy, è chiaro che questo oggetto è un cane. Ma non hai ancora aggiunto alcun attributo. Diamo alla classe Dog un nome e un’età riscrivendola:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
Ora vedi che la funzione accetta due argomenti dopo self: name e age. Questi vengono poi assegnati rispettivamente a self.name e self.age. Ora puoi creare un nuovo oggetto ozzy con un nome e un’età:
ozzy = Dog("Ozzy", 2)
Per accedere agli attributi di un oggetto in Python, puoi usare la dot notation. Si fa digitando il nome dell’oggetto, seguito da un punto e dal nome dell’attributo
print(ozzy.name)
print(ozzy.age)
Ozzy
2
Questo può anche essere combinato in una frase più articolata:
print(ozzy.name + " is " + str(ozzy.age) + " year(s) old.")
Ozzy is 2 year(s) old.
La funzione str() viene usata qui per convertire l’attributo age, che è un intero, in una stringa, così puoi usarlo nella funzione print().
Definire metodi in una classe
Ora che hai una classe Dog, ha un nome e un’età di cui puoi tenere traccia, ma in realtà non fa nulla. Qui entrano in gioco i metodi di istanza. Puoi riscrivere la classe per includere ora un metodo bark(). Nota come si usa di nuovo la keyword def, così come l’argomento self.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
Il metodo bark ora può essere chiamato usando la dot notation, dopo aver istanziato un nuovo oggetto ozzy. Il metodo dovrebbe stampare "bark bark!" a schermo. Nota le parentesi in .bark(). Si usano sempre quando si chiama un metodo. In questo caso sono vuote, perché il metodo bark() non accetta argomenti.
ozzy = Dog("Ozzy", 2)
ozzy.bark()
bark bark!
Ricordi come hai stampato ozzy prima? Il codice sotto ora implementa questa funzionalità nella classe Dog, con il metodo doginfo(). Poi istanzi alcuni oggetti con proprietà diverse e chiami il metodo su di essi.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
ozzy = Dog("Ozzy", 2)
skippy = Dog("Skippy", 12)
filou = Dog("Filou", 8)
ozzy.doginfo()
skippy.doginfo()
filou.doginfo()
Ozzy is 2 year(s) old.
Skippy is 12 year(s) old.
Filou is 8 year(s) old.
Come vedi, puoi chiamare il metodo doginfo() sugli oggetti con la dot notation. La risposta ora dipende su quale oggetto Dog stai chiamando il metodo.
Poiché i cani invecchiano, sarebbe utile poter aggiornare di conseguenza la loro età. Ozzy ha appena compiuto 3 anni, quindi cambiamo la sua età.
ozzy.age = 3
print(ozzy.age)
3
È semplice come assegnare un nuovo valore all’attributo. Potresti anche implementarlo come metodo birthday() nella classe Dog:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
ozzy = Dog("Ozzy", 2)
print(ozzy.age)
2
ozzy.birthday()
print(ozzy.age)
3
Ora non devi più cambiare manualmente l’età del cane. Ogni volta che è il suo compleanno, puoi semplicemente chiamare il metodo birthday().
Passare argomenti ai metodi
Vorresti che i nostri cani avessero un amico. Questo dovrebbe essere opzionale, dato che non tutti i cani sono così socievoli.
Dai un’occhiata al metodo setBuddy() qui sotto. Accetta come argomenti self, come al solito, e buddy. In questo caso, buddy sarà un altro oggetto Dog. Imposta l’attributo self.buddy su buddy, e l’attributo buddy.buddy su self.
Questo significa che la relazione è reciproca; tu sei l’amico del tuo amico. In questo caso, Filou sarà l’amico di Ozzy, il che significa che Ozzy diventa automaticamente l’amico di Filou.
Potresti anche impostare questi attributi manualmente invece di definire un metodo, ma richiederebbe più lavoro (scrivere due righe di codice invece di una) ogni volta che vuoi impostare un amico.
Nota che in Python non devi specificare di che tipo è l’argomento. Se fosse Java, sarebbe richiesto.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
def setBuddy(self, buddy):
self.buddy = buddy
buddy.buddy = self
Ora puoi chiamare il metodo con la dot notation e passargli un altro oggetto Dog. In questo caso, l’amico di Ozzy sarà Filou:
ozzy = Dog("Ozzy", 2)
filou = Dog("Filou", 8)
ozzy.setBuddy(filou)
Se ora vuoi ottenere qualche informazione sull’amico di Ozzy, puoi usare la dot notation due volte: la prima per riferirti all’amico di Ozzy, e la seconda per riferirti al suo attributo.
print(ozzy.buddy.name)
print(ozzy.buddy.age)
Filou
8
Nota come questo si possa fare anche per Filou.
print(filou.buddy.name)
print(filou.buddy.age)
Ozzy
2
Si possono anche chiamare i metodi dell’amico. L’argomento self che viene passato a doginfo() ora è ozzy.buddy, cioè filou.
ozzy.buddy.doginfo()
Filou is 8 year(s) old.
Esempio di OOP in Python
Un esempio in cui la programmazione orientata agli oggetti in Python può tornare utile è il nostro tutorial Python per la finanza: trading algoritmico. In esso spieghiamo come impostare una strategia di trading per un portafoglio azionario.
La strategia di trading si basa sulla media mobile del prezzo di un’azione. Se signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:] è soddisfatta, viene creato un segnale. Questo segnale è una previsione per la variazione futura del prezzo del titolo.
Nel codice qui sotto, vedrai che c’è prima un’inizializzazione, seguita dal calcolo della media mobile e dalla generazione del segnale. Poiché non è codice orientato agli oggetti, è solo un unico blocco grande che viene eseguito in una volta.
Nota che usiamo aapl nell’esempio, che è il ticker azionario di Apple. Se volessi farlo per un’azione diversa, dovresti riscrivere il codice.
# Initialize
short_window = 40
long_window = 100
signals = pd.DataFrame(index=aapl.index)
signals['signal'] = 0.0
# Create short simple moving average over the short window
signals['short_mavg'] = aapl['Close'].rolling(window=short_window, min_periods=1, center=False).mean()
# Create long simple moving average over the long window
signals['long_mavg'] = aapl['Close'].rolling(window=long_window, min_periods=1, center=False).mean()
# Create signals
signals['signal'][short_window:] = np.where(signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:], 1.0, 0.0)
# Generate trading orders
signals['positions'] = signals['signal'].diff()
# Print `signals`
print(signals)
Con un approccio orientato agli oggetti, devi scrivere il codice di inizializzazione e generazione dei segnali solo una volta.
Puoi poi creare un nuovo oggetto per ogni titolo su cui vuoi calcolare una strategia e chiamare su di esso il metodo generate_signals(). Nota che il codice OOP è molto simile al codice sopra, con l’aggiunta di self.
class MovingAverage():
def __init__(self, symbol, bars, short_window, long_window):
self.symbol = symbol
self.bars = bars
self.short_window = short_window
self.long_window = long_window
def generate_signals(self):
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = 0.0
signals['short_mavg'] = self.bars['Close'].rolling(window=self.short_window, min_periods=1, center=False).mean()
signals['long_mavg'] = bars['Close'].rolling(window=self.long_window, min_periods=1, center=False).mean()
signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] > signals['long_mavg'][self.short_window:], 1.0, 0.0)
signals['positions'] = signals['signal'].diff()
return signals
Ora puoi semplicemente istanziare un oggetto con i parametri che vuoi e generare i segnali per esso.
apple = MovingAverage('aapl', aapl, 40, 100)
print(apple.generate_signals())
Fare lo stesso per un altro titolo diventa molto semplice. È solo questione di istanziare un nuovo oggetto con un simbolo diverso.
microsoft = MovingAverage('msft', msft, 40, 100)
print(microsoft.generate_signals())
Considerazioni finali
Abbiamo coperto alcuni dei principali concetti OOP in Python. Ora sai come dichiarare classi e metodi, istanziare oggetti, impostarne gli attributi e chiamare metodi di istanza. Queste competenze torneranno utili nella tua futura carriera da data scientist.
Per un approccio più moderno alla definizione delle classi, dai un’occhiata alle data class di Python, che riducono il boilerplate. Se vuoi approfondire i concetti chiave necessari per lavorare ulteriormente con Python, non perderti il nostro corso Python intermedio.
Con l’OOP, il tuo codice crescerà in complessità man mano che il programma diventa più grande. Avrai classi diverse, sottoclassi, oggetti, ereditarietà, metodi di istanza e altro. Per scenari di ereditarietà complessi, scopri l’ereditarietà multipla e super().
FAQ su OOP in Python
Che cos’è la programmazione orientata agli oggetti (OOP)?
La programmazione orientata agli oggetti è un paradigma basato sul concetto di "oggetti", che possono contenere dati e codice che manipola tali dati. Nell’OOP, gli oggetti vengono creati da modelli chiamati "classi", che definiscono le proprietà e il comportamento degli oggetti che creano. L’OOP ti permette di creare codice riutilizzabile e di modellare più da vicino concetti del mondo reale, rendendola una scelta popolare per molti progetti software.
Cosa sono classi e oggetti in Python?
In Python, una classe è un modello per creare oggetti. Definisce le proprietà e il comportamento degli oggetti creati a partire da essa. Un oggetto è un’istanza di una classe, creata chiamando la classe come una funzione. L’oggetto contiene i dati e il comportamento definiti dalla classe, oltre a un’identità univoca.
Come definisco una classe in Python?
Per definire una classe in Python, usi la keyword class, seguita dal nome della classe e dai due punti. La definizione della classe è indentata e il blocco indentato contiene le proprietà e i metodi (funzioni) che appartengono alla classe.
Come creo un oggetto da una classe in Python?
Per creare un oggetto da una classe in Python, chiami la classe come una funzione, passando gli eventuali argomenti necessari al costruttore della classe (il metodo __init__).
Quali sono i quattro pilastri dell’OOP in Python?
I quattro pilastri della programmazione orientata agli oggetti sono: Incapsulamento (raggruppare dati e metodi limitandone l’accesso), Ereditarietà (consentire alle classi di ereditare attributi e metodi da classi genitore), Polimorfismo (permettere di trattare in modo uniforme oggetti di tipi diversi) e Astrazione (nascondere dettagli implementativi complessi esponendo solo le funzionalità essenziali).
Qual è la differenza tra attributo di classe e attributo di istanza?
Un attributo di classe è condiviso da tutte le istanze di una classe ed è definito direttamente nel corpo della classe. Un attributo di istanza è unico per ogni oggetto ed è definito all’interno del metodo __init__ usando self. Per esempio, tutti i cani potrebbero condividere un attributo di classe species, ma ciascun cane ha il proprio attributo di istanza name.