Python - Programmazione Object Oriented

Python

Ho sempre ritenuto la Programmazione Orientata agli Oggetti (OOP nel seguito) uno dei paradigmi di programmazione più interessanti per come riesca a rendere semplice e dinamica l'estensione di un codice, nonchè per l'ordine e per la pulizia che si ottiene utilizzando una convenzione precisa.
Data anche la mia passione per Python, ho deciso negli ultimi giorni di leggere articoli su blog e guardare diversi tutorial sull'argomento OOP, allo scopo di migliorare il mio stile di programmazione. Come esercizio, ho ripreso in mano il progetto sul Fantacalcio presentato in questo articolo ed eseguito un refactoring massiccio. Il risultato lo potete trovare adesso sul nostro repository.

In questo breve articolo vedremo perciò alcuni dei concetti principali della OOP in Python. Il testo non ha infatti la pretesa di esaurire l'argomento, bensì di introdurre il paradigma a chi con Python ha sempre preferito un approccio più "strutturale".


Cos'è la OOP?

Sono confidente sul fatto che chi sta leggendo questo articolo già sappia, anche solo a grandi linee, cosa si intende per OOP. Se volessimo trovare una definizione informale, possiamo dire che la OOP è un tipo di programmazione in cui si dà importanza alle entità in gioco all'interno di un sistema e a come tali entità interagiscono tra loro. Elenchiamo velocemente nel seguito le caratteristiche principali del paradigma:

  • Concetti di Classe e Oggetto: la Classe è il "blueprint" di un'entità; l'Oggetto è una delle istanze dell'entità.
  • Attributi e Metodi: ogni Classe contiene Attributi che definiscono lo stato delle istanze, e Metodi che definiscono funzionalità proprie dell'istanza; si distinguono inoltre Metodi e Attributi di Classe dai più comuni Metodi e Attributi di Istanza.
  • Incapsulamento: lo stato degli Oggetti, gli Attributi ed i Metodi sono mantenuti e definiti all'interno dell'Oggetto stesso.
  • Ereditarietà: è possibile definire Classi che "estendono" le caratteristiche di altre Classi; classico esempio della Classe Cane che estende la Classe Animale.
  • Polimorfismo: concetto che si lega fortemente alla non ripetizione del codice; più Oggetti di Classi che derivano da una Classe madre possono accedere agli stessi metodi definiti per la Classe madre, senza che questi siano ripetuti nelle definizioni delle Classi figlie; l'esempio delle Classi Cane e Gatto che ereditano da Animale e condividono il metodo cammina().
  • Overriding: le Classi figlie possono ridefinire metodi della Classe madre per scopi più legati alle loro necessità; ancora l'esempio delle Classi Cane e Gatto che ereditano da Animale e ridefiniscono il metodo emetti_verso() con print 'Bau!' e print 'Miao!'

Per delle definizioni più formali e precise, vi invito a cercare su internet testi più tecnici di questo articolo e a non saltare le lezioni di Programmazione Orientata agli Oggetti all'Università.


Classi in Python

Prendiamo adesso come esempio una versione semplificata della classe Giornata del nostro progettino sul Fantacalcio:



Intestazione

Per definire una Classe in Python si utilizza la parola chiave class. Nel caso di Giornata abbiamo:
class Giornata(object):
Ciò che viene inserito tra parentesi dopo l'identificatore della Classe è l'elenco delle classi dalle quali deriva la Classe che stiamo definendo. Come già detto nel nostro articolo introduttivo, in Python tutto deriva dalla classe object. In Python 3.x non è più necessario esplicitare l'ereditarietà con object, mentre in Python 2.x è consigliabile esplicitarla per ogni definizione di classe, come fatto nell'esempio di sopra.
Se l'ereditarietà non è esplicitata, si può incorrere a problemi nel richiamare funzioni delle classi madre.
Ecco per esempio cosa succede se richiamiamo il metodo type() per classi definite con l'ereditarietà esplicita a object e per classi senza la dichiarazione esplicita:

Python class

Nel primo caso avremo come risposta una istanza, nel secondo avremo come risposta una classe.
Oltre a ciò, se non esplicitata l'eredtarietà con object si possono avere problemi con il metodo super(), in caso di ereditarietà multiple, e con la definizione delle @property, le quali saranno trattate nei paragrafi a seguire.


Il "costruttore"

Il metodo __init__() è un metodo all'interno del quale avviene l'inizializzazione degli attributi. Provenendo dalla OOP in Java e C++, spesso viene chiamato "costruttore". In realtà, quando il metodo __init__() è richiamato, esso prende in ingresso come primo parametro un'istanza già creata (self) e ne inizializza gli attributi. Non si occupa preciò della instanziazione del nuovo Oggetto.

La variabile self deve essere quindi passata come primo parametro ad ogni metodo di istanza. Tale variabile serve per accedere agli attributi e ai metodi propri della istanza che si sta utilizzando. Faccio notare che self NON è una parola chiave: se al posto di self utilizzaste sempre pippo come primo parametro dei metodi di istanza nel vostro codice, il programma funzionerebbe lo stesso. Tuttavia per convenzione è buona regola utilizzare self, così da non compromettere la leggibilità del vostro codice.


Le @property

In Python non esiste il concetto di "scope delle variabili": tutti gli attributi di un Oggetto sono pubblici e possono essere acceduti da chiunque tramite un'istanza o una classe. Se avete già avuto a che fare con la OOP in Java o C++ sarete tutti abituati a lasciare i vostri attributi privati e scrivere metodi getter e setter pubblici per gestirli. In Python esistono due possibilità:

  • Sapere che chiunque può accedere direttamente agli attributi di una istanza e riassegnarli a piacimento, e quindi convivere con questo fatto.
  • Utilizzare le property come fossero getter e setter

Definendo delle property, ogni volta che richiamiamo un attributo, quello che stiamo realmente facendo è richiamare dei metodi per assegnare o restituire i valori dei veri attributi "privati" della nostra Classe (per convenzione, variabili e metodi che iniziano con __ sono considerati "privati" in Python).

Risulta consigliabile utilizzare delle property quando si vuole cambiare il comportamento di un attributo, senza modificarne l'interfaccia per accedervi.

Metodi built-in di object

Abbiamo detto che la Classe Giornata deriva dalla Classe object, ereditandone i metodi e gli attributi. Esistono dei metodi speciali, detti built-in, che possono essere richiamati dalle istanze. Si distinguono dai metodi standard dai doppi underscore come prefisso e suffisso del nome. Il metodo __init__(), del quale abbiamo parlato in precedenza, rientra nella categoria. Nell'esempio, abbiamo fatto l'override del metodo __str__(), metodo che viene richiamato ogni volta che facciamo un print di un oggetto e che deve perciò restituire una stringa. Per un elenco completo degli altri metodi, vi invito a leggere la documentazione di Python.

Metodi "di istanza" e "di classe"

Vediamo infine le differenze tra i metodi detti "di istanza" ed i metodi "di classe".
I primi sono metodi che vengono richiamati direttamente dalle singole istanze di una Classe. Sono perciò caratterizzati da un comportamento che dipende direttamente dallo stato interno dell'Oggetto. Nell'esempio, il metodo get_nome_giornata() è un metodo di istanza, il cui output dipende dal valore dell'attributo di istanza n_giornata.

Se volessimo invece aggiungere funzionalità alla nostra Classe che non dipendono direttamente dalle istanze, e che quindi possono essere richiamate senza dover forzatamente creare un Oggetto, dobbiamo definire dei metodi di classe.

Riprendendo l'esempio, potremmo scrivere:

La funzione get_nome_campionato() è definita con il tag @staticmethod il quale dice all'interprete Python che tale funzione può essere chiamata da una istanza della Classe, ma anche dalla Classe stessa:
print Giornata.get_nome_campionato()


Come sempre, vi invito a lasciare nei commenti i vostri dubbi, domande, le vostre precisazioni o aggiunte.
Vi invito inoltre a segnalarci eventuali bug o problemi riscontrati nell'utilizzo del codice.

Alessio Antonielli

Ingegnere informatico, appassionato di cinema, musica, videogiochi e serie tv. Insomma, le solite cose che vi aspettereste da un ex studente di Ingegneria Informatica, giusto per rafforzare lo stereotipo…

Python - Programmazione Object Oriented ultima modifica: 2016-11-29T12:20:00+01:00 da Alessio Antonielli


Advertisment ad adsense adlogger