12. Classi e oggetti
Una classe è il modello da cui vengono creati gli oggetti. Si dice che un oggetto sia un'istanza di una classe.

Nota: la cartella [shared] è stata inserita nella directory [Root Sources] del progetto.
12.1. script [classes_01]: una classe Object
Lo script [classes_01] mostra un uso obsoleto delle classi:
Note:
- righe 2-3: una classe [Object] vuota;
- Riga 2: La classe può essere dichiarata in tre forme:
- class Object;
- class Object();
- class Object(object);
- riga 3: un'altra forma di commento. Questo, preceduto da tre "", può estendersi su più righe;
- riga 7: istanziazione della classe Object. Il risultato è un indirizzo, come mostrato nelle righe 24–26;
- righe 8–9: inizializzazione diretta di due attributi dell'oggetto;
- riga 17: copia dei riferimenti. Le variabili obj1 e obj2 sono due puntatori (riferimenti) allo stesso oggetto;
- riga 19: modifichiamo l'oggetto puntato da [obj2]. Poiché [obj1] e [obj2] puntano allo stesso oggetto, la visualizzazione degli oggetti [obj1, obj2] nelle righe 21 e 22 mostrerà che l'oggetto puntato da [obj1] è cambiato;
- righe 24–26: queste righe hanno lo scopo di dimostrare che le variabili [obj1] e [obj2] sono uguali. L'output della riga 26 lo confermerà. In questo confronto, sono gli indirizzi di [obj1] e [obj2] ad essere uguali;
- Ogni oggetto Python è identificato da un ID univoco ottenuto utilizzando l'espressione [id(object)]. Le righe 24 e 25 mostreranno che gli ID degli oggetti a cui puntano [obj1] e [obj2] sono identici, dimostrando così che questi due riferimenti puntano allo stesso oggetto;
- Righe 27–29: La funzione [isinstance(expr, Type)] restituisce il valore booleano True se l'espressione [expr] è di tipo [Type]. Qui vedremo che [obj1] è di tipo [Object], il che sembra naturale, ma anche di tipo [object]. La classe [object] è la classe padre di tutte le classi Python. Per la proprietà dell'ereditarietà delle classi, una classe figlia F possiede tutte le proprietà della sua classe padre P, e la funzione [isinstance(istanza di F, P)] restituisce True;
- righe 30–32: mostrano che il tipo [int] è anche un tipo [object]. Tutti i tipi Python derivano dalla classe [object];
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_01.py
objet1=[<__main__.Objet object at 0x0000025C3F469BB0>, <class '__main__.Objet'>,2595221838768,un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
objet1=[<__main__.Objet object at 0x0000025C3F469BB0>, 2595221838768,un,0]
objet2=[<__main__.Objet object at 0x0000025C3F469BB0>, 2595221838768,un,0]
True
type(obj1)=<class '__main__.Objet'>
isinstance(obj1,Objet)=True, isinstance(obj1,object)=True
type(4)=<class 'int'>
isinstance(4, int)=True, isinstance(4, object)=True
Process finished with exit code 0
12.2. Script [classes_02]: una classe Person
Lo script [classes_02] dimostra che gli attributi di una classe sono pubblici: è possibile accedervi direttamente dall'esterno della classe. Questo è un altro esempio di utilizzo di una classe che non è raccomandato. Lo includiamo, tuttavia, perché potresti occasionalmente imbatterti in questo tipo di codice (Python lo consente) e devi essere in grado di comprenderlo.
Note:
- righe 2–9: una classe con un metodo;
- riga 7: ogni metodo di una classe deve avere l'oggetto self, che fa riferimento all'oggetto corrente, come primo parametro. Il metodo [identity] restituisce una stringa;
- riga 15: istanziazione di un oggetto [Person];
- righe 16–19: mostrano che gli attributi dell'oggetto possono essere creati dinamicamente (non esistono nella definizione della classe);
- riga 9: gli attributi della classe sono indicati con la notazione [self.attribute];
- righe 23–24 dimostrano che l'oggetto [p] è sia un'istanza della classe [Person] che della classe [object];
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_02.py
personne=[Paul,de la Hûche,48]
type(p)=<class '__main__.Personne'>
isinstance(Personne)=True, isinstance(object)=True
Process finished with exit code 0
12.3. Script [classes_03]: la classe Person con un costruttore
Lo script [classes_03] illustra l'uso standard di una classe:
Note:
- riga 4: il costruttore della classe si chiama __init__. Come per gli altri metodi, il suo primo parametro è self;
- riga 20: viene creato un oggetto Person utilizzando il costruttore della classe;
- righe 13–15: il metodo [identity] restituisce una stringa che rappresenta il contenuto dell'oggetto;
- riga 22: visualizza l'identità della persona;
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_03.py
personne=[Paul,de la Hûche,48]
Process finished with exit code 0
12.4. Script [classes_04]: metodi statici
Definiamo la seguente classe [Utils] (utils.py) nella cartella [modules]:

Note
- Riga 3: L'annotazione [@staticmethod] indica che il metodo contrassegnato con essa è un metodo di classe, non un metodo di istanza. Ciò è evidente dal fatto che il primo parametro del metodo annotato non è la parola chiave [self]. Pertanto, il metodo statico non ha accesso agli attributi dell'oggetto. Invece di scrivere:
scriviamo
Poiché abbiamo scritto [Utils.is_string_ok] sopra, il metodo [is_string_ok] è chiamato metodo di classe (la classe Utils in questo caso). Per ottenere questo risultato, il metodo [Utils.is_string_ok] deve essere annotato con la parola chiave [@staticmethod].
Il metodo statico [Utils.is_string_ok] ci permette di verificare in questo caso che un dato sia una stringa non vuota.
Lo script [classes_04] utilizza la classe [Utils] come segue:
- Righe 1-4: Utilizziamo uno script di configurazione;
Lo script di configurazione [config.py] è il seguente:
- Riga 9: La cartella [shared] verrà aggiunta al Python Path;
Il risultato dell'esecuzione è il seguente:
12.5. Script [classes_05]: Controlli di validità degli attributi
Lo script [classes_05] introduce nuovi concetti:
- definizione di un tipo di eccezione personalizzato;
- definizione del metodo [_str_], che è il metodo di identità predefinito per le classi;
- definizione delle proprietà;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
Note:
- righe 10–13: una classe MyException derivata dalla classe BaseException (tratteremo questo punto più avanti). Non aggiunge alcuna funzionalità a quest'ultima. È presente solo per fornire un'eccezione personalizzata;
- riga 19: il costruttore ha valori predefiniti per i suoi parametri. Pertanto, l'operazione p = Person() è equivalente a p = Person("x", "y", 0);
- righe 34–45: le proprietà della classe. Si tratta di metodi annotati con la parola chiave [@property]. Sono utilizzati per impostare i valori degli attributi;
- righe 47–77: i setter della classe. Si tratta di metodi annotati con la parola chiave [@attributsetter]. Sono utilizzati per impostare il valore degli attributi;
- righe 48–54: il setter per l'attributo [first_name]. Questo metodo verrà chiamato ogni volta che viene assegnato un valore all'attributo [first_name]:
La riga 2 attiverà la chiamata [p.firstName(value)]. Il vantaggio di utilizzare un setter per assegnare un valore a un attributo è che, poiché il setter è una funzione, possiamo verificare la validità del valore assegnato all'attributo;
- riga 51: verifichiamo che il valore assegnato all'attributo [first_name] sia una stringa non vuota. A tal fine, utilizziamo il metodo statico [Utils.isStringOk] visto in precedenza;
- riga 52: il valore assegnato all'attributo [first_name] viene privato degli spazi iniziali e finali e assegnato all'attributo [self.__first_name]. Pertanto, l'attributo [first_name] stesso non viene utilizzato qui. Non avremmo potuto fare altrimenti, altrimenti avremmo avuto una chiamata ricorsiva infinita. Avremmo potuto utilizzare qualsiasi nome di attributo. Il fatto che abbiamo utilizzato l'attributo [__prénom] con due trattini bassi all'inizio dell'identificatore ha un significato speciale: gli attributi preceduti da due trattini bassi sono privati della classe. Ciò significa che non sono visibili dall'esterno della classe. Pertanto, non possiamo scrivere:
In realtà, vedremo presto che è possibile scriverlo, ma non modifica il nome. Fa qualcos’altro;
- righe 53–54: se il valore assegnato a first_name è errato, viene generata un'eccezione. In questo modo, il codice chiamante saprà che la sua chiamata è errata;
- righe 35–37: la proprietà [first_name]. Verrà chiamata ogni volta che [p.first_name] viene scritto in un'espressione. Verrà quindi chiamato il metodo [p.first_name()]. Riga 37: restituiamo il valore dell'attributo [__first_name], poiché abbiamo visto che il setter per l'attributo [first_name] assegna il suo valore all'attributo privato [__first_name];
- Righe 56–62: il setter per l'attributo [last_name] è costruito in modo simile a quello dell'attributo [first_name]. Lo stesso vale per il setter dell'attributo [age] alle righe 64–77;
- sebbene le proprietà [first_name, last_name, value] non siano gli attributi effettivi — che sono in realtà [__first_name, __last_name, __age] — continueremo a riferirci a loro come attributi della classe, poiché vengono utilizzati come tali;
- righe 19–28: Il costruttore della classe utilizza implicitamente i setter per gli attributi [first_name, last_name, age]. Infatti, scrivendo [self.first_name = first_name] alla riga 26, viene implicitamente chiamato il metodo [first_name(self, first_name)]. Verrà quindi verificata la validità del parametro [first_name]. Lo stesso vale per gli altri due attributi [last_name, age];
- con questo modello, non è possibile assegnare valori errati agli attributi della classe [first_name, last_name, age];
- righe 30–32: la funzione __str__ sostituisce il metodo precedentemente chiamato identity. Il nome [__str__] (due trattini bassi prima e dopo) non è insignificante. Lo vedremo più avanti;
- righe 83–86: istanziamento di una persona, seguito dalla visualizzazione della sua identità;
- riga 84: istanziazione;
- riga 86: visualizzazione. L'operazione richiede la visualizzazione dell'oggetto p come stringa. L'interprete Python chiama automaticamente il metodo p.__str__() se esiste. Questo metodo ha lo stesso scopo del metodo toString() nei linguaggi Java o .NET;
- righe 87–89: gestione di una potenziale MyException. Quindi visualizza l'errore;
- righe 91–99: come sopra per una seconda persona istanziata con parametri errati;
- righe 102–109: come sopra per una terza persona istanziata con parametri predefiniti: non vengono passati parametri. Qui vengono quindi utilizzati i valori predefiniti di questi parametri nel costruttore;
- righe 112–117: abbiamo detto che l'attributo [__first_name] era privato e quindi normalmente inaccessibile dall'esterno della classe. Vogliamo verificarlo;
- righe 112–114: assegniamo un valore all'attributo [__first_name], quindi controlliamo i valori degli attributi [__first_name] e [first_name], che normalmente dovrebbero essere gli stessi;
- righe 115–117: Ripetiamo l'operazione, questa volta inizializzando l'attributo [first_name];
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_05.py
personne=[Paul,de la Hûche,48]
L'âge doit être un entier >=0
personne=[x,y,0]
p.prénom=x
p.__prénom=Gaëlle
p.prénom=Sébastien
p.__prénom=Gaëlle
Process finished with exit code 0
Note
- righe 5-6: vediamo che l'assegnazione [p.__first_name = "Gaëlle"] non ha modificato il valore dell'attributo [first_name], riga 5;
- righe 7-8: vediamo che l'assegnazione [p.first_name = "Sébastien"] non ha modificato il valore dell'attributo [__first_name], riga 8;
Cosa possiamo concludere da questo? Che probabilmente l'operazione [p.__first_name = "Gaëlle"] ha creato un attributo pubblico [__first_name] per la classe, ma che questo è diverso dall'attributo privato [__first_name] manipolato al suo interno;
12.6. Script [classes_06]: Aggiunta di un metodo di inizializzazione dell'oggetto
Lo script [classes_06] aggiunge un metodo alla classe [Person]:
Note:
- La differenza rispetto allo script precedente si trova nelle righe 30–33. Abbiamo aggiunto il metodo initWithPerson. Questo metodo chiama il costruttore __init__. A differenza dei linguaggi tipizzati, non è possibile avere metodi con lo stesso nome che si distinguono per la natura dei loro parametri o dei loro valori di ritorno. Pertanto, non è possibile avere più costruttori che creerebbero l'oggetto a partire da parametri diversi, in questo caso un oggetto di tipo Person;
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_06.py
personne=[Paul,de la Hûche,48]
L'âge doit être un entier >=0
p=[x,y,0]
p2=[x,y,0]
Process finished with exit code 0
12.7. Script [classes_07]: un elenco di oggetti Person
Ora inseriremo le classi [MyException] e [Person] in un modulo in modo da poterle utilizzare senza dover copiare il loro codice:

Entrambe le classi saranno nel modulo [myclasses.py] sopra.
Lo script [classes_07] mostra che possiamo avere un elenco di oggetti:
Note:
- riga 7: importiamo la classe [Person];
- riga 11: un elenco di oggetti di tipo [Person];
- righe 13–14: iteriamo attraverso questo elenco per visualizzare ciascuno dei suoi elementi;
- riga 14: la funzione [print] visualizzerà la stringa che rappresenta l'oggetto [group[i]]. Per impostazione predefinita, verrà chiamato il metodo [__str__] di questi oggetti;
Risultati
C:\Users\serge\.virtualenvs\cours-python-v02\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/v-02/classes/01/classes_07.py
groupe[0]=[Paul,Langevin,48]
groupe[1]=[Sylvie,Lefur,70]
Process finished with exit code 0
12.8. Script [classes_08]: creazione di una classe derivata dalla classe Person
Definiamo la seguente classe [Teacher] nel modulo [myclasses]:
# classe Enseignant
class Enseignant(Personne):
# constructeur
def __init__(self, prénom: str = "x", nom: str = "x", âge: int = 0, discipline: str = "x"):
# prénom : prénom de la personne
# nom : nom de la personne
# âge : âge de la personne
# discipline : discpline enseignée
# initialisation du parent
Personne.__init__(self, prénom, nom, âge)
# autres initialisations
self.discipline = discipline
# toString
def __str__(self) -> str:
return f"enseignant[{super().__str__()},{self.discipline}]"
# propriétés
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# la discipline doit être une chaîne non vide
if Utils.is_string_ok(discipline):
self.__discipline = discipline
else:
raise MyException("La discipline doit être une chaîne de caractères non vide")
- Riga 2: dichiara la classe Teacher come sottoclasse della classe Person. Una sottoclasse possiede tutte le proprietà (attributi e metodi) della sua classe padre oltre alle proprie;
- riga 13: la classe [Teacher] definisce un nuovo attributo [subject];
- riga 11: il costruttore della classe derivata Teacher deve chiamare il costruttore della classe padre Person, passando i parametri che si aspetta;
- riga 17: la funzione [super()] chiama la classe padre. Qui, chiamiamo la funzione [__str__] della classe padre;
- righe 19–30: vengono definiti il getter e il setter per il nuovo attributo [subject];
Lo script [classes_08] utilizza la classe [Teacher] come segue:
Note:
- riga 7: importiamo le classi [Person] e [Teacher] definite nel file [myclasses.py];
- righe 11–14: definiamo un gruppo di persone e poi visualizziamo le loro identità;
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_08.py
groupe[0]=enseignant[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
Process finished with exit code 0
12.9. Script [classes_09]: seconda classe derivata dalla classe Person
Lo script [classes_09] introduce la classe [Student] derivata dalla classe [Person]. Questa è definita come segue nel modulo [myclasses]:
# classe Etudiant
class Etudiant(Personne):
# constructeur
def __init__(self: object, prénom: str = "x", nom: str = "y", âge: int = 0, formation: str = "x"):
Personne.__init__(self, prénom, nom, âge)
self.formation = formation
# toString
def __str__(self: object) -> str:
return f"étudiant[{super().__str__()},{self.formation}]"
# propriétés
@property
def formation(self) -> str:
return self.__formation
@formation.setter
def formation(self, formation: str):
# la formation doit être une chaîne non vide
if Utils.is_string_ok(formation):
self.__formation = formation
else:
raise MyException("La formation doit être une chaîne de caractères non vide")
Lo script [classes_09] utilizza la classe [Student] come segue:
Note:
- Questo script è simile al precedente.
Risultati
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_09.py
enseignant[[Paul,Langevin,48],anglais]
[Sylvie,Lefur,70]
étudiant[[Steve,Boer,22],iup2 qualité]
Process finished with exit code 0
12.10. Script [classes_10]: la proprietà [__dict__]
Lo script [classes_10] introduce la proprietà [__dict__], che useremo spesso in seguito:
Commenti
- righe 1-4: l'applicazione viene configurata;
- riga 7: viene importata la classe [Student];
- riga 11: istanziamento di uno studente;
- riga 13: uso del metodo predefinito [__dict__] (due trattini bassi prima e dopo l'identificatore);
I risultati sono i seguenti:
- alla riga 2, otteniamo un dizionario le cui chiavi sono le proprietà dell'oggetto precedute dal nome della classe a cui appartengono. Useremo questo dizionario per creare un collegamento tra l'oggetto e il dizionario;