Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Classi in Python

Le classi sono i costrutti fondamentali della programmazione a oggetti: ecco come definirle ed utilizzarle quando programmiamo in Python.
Le classi sono i costrutti fondamentali della programmazione a oggetti: ecco come definirle ed utilizzarle quando programmiamo in Python.
Link copiato negli appunti

Nella precedente lezione abbiamo introdotto i concetti principali della programmazione ad oggetti e abbiamo visto alcuni semplici esempi in Python. In questa lezione vedremo in maggiore dettaglio come definire classi, metodi e attributi, e come usarli. Vedremo inoltre come creare istanze e sottoclassi.

Definire classi

La classe più semplice che possiamo definire in Python è:

>>> class Test:
...     pass
...

Come possiamo vedere dall'esempio, per definire una classe basta usare la parola chiave class, seguita dal nome che vogliamo dare alla classe (in questo caso Test), seguita dai due punti (:), seguita infine da un blocco di codice indentato (in questo caso c'è solo il pass).

Questa sintassi è simile a quella usata per definire le funzioni, con la differenza che si usa la parola chiave class invece di def e che la lista di argomenti dopo il nome non è presente. È inoltre importante notare che, per convenzione, i nomi delle classi generalmente usano il CamelCase, fatta eccezione per alcuni tipi di base come int, str, list, dict, ecc.

Una volta definita la classe Test, possiamo verificare che il nome Test fa riferimento alla classe. Possiamo quindi usare la classe per creare diverse istanze, semplicemente usando le () per "chiamarla":

>>> Test  # Test si riferisce alla classe
<class '__main__.Test'>
>>> Test()  # Test() ritorna una nuova istanza
<__main__.Test object at 0x7f87ed6a26d8>
>>> Test()  # Test() ritorna una nuova istanza
<__main__.Test object at 0x7f87ed6a26a0>

Ogni chiamata ci restituisce una nuova e separata istanza, come si può vedere dal diverso ID delle due istanze create nell'esempio (0x7f87ed6a26d8 e 0x7f87ed6a26a0).

Si noti che in Python 2 è importante definire le classi usando la sintassi class Test(object): ... per fare in modo che ereditino da object. In Python 3 tutte le classi ereditano automaticamente da object.

Metodi

Nella precedente lezione abbiamo anche visto che i metodi sono simili a funzioni definite all'interno della classe:

>>> # definiamo una classe Test
>>> class Test:
...     # definiamo un metodo che stampa un messaggio
...     def method(self):
...         print('method called')
...
>>> inst = Test()  # creiamo un'istanza di Test
>>> inst.method()  # chiamiamo il metodo dell'istanza
method called

Da questo esempio possiamo notare diverse cose:

  • le definizioni dei metodi si trovano indentate all'interno della classe;

  • la sintassi usata per definire i metodi è uguale a quella usata per definire le funzioni;
  • i metodi devono definire un parametro aggiuntivo che per convenzione è chiamato self;
  • per chiamare un metodo basta usare la sintassi istanza.metodo().

self

Come si nota nell'esempio precedente, la differenza più importante tra la definizione di metodi e funzioni, è la presenza del self. self è un argomento che si riferisce all'istanza, e anche se i metodi devono dichiararlo esplicitamente, non è necessario passarlo esplicitamente. Vediamo un altro esempio per capire meglio il funzionamento di self:

>>> # definiamo una classe Test
>>> class Test:
...     # definiamo un metodo che stampa self
...     def method(self):
...         print('self is:', self)
...
>>> inst = Test()  # creiamo un'istanza di Test
>>> inst  # mostriamo l'id dell'istanza
<__main__.Test object at 0x7fef42c03f28>
>>> inst.method()  # verifichiamo che self corrisponde all'istanza
self is: <__main__.Test object at 0x7fef42c03f28>

Il motivo per cui non è necessario passare il self esplicitamente è che l'espressione inst.method() è semplicemente zucchero sintattico per Test.method(inst):

>>> Test.method  # Test.method è una funzione definita nella classe
<function Test.method at 0x7fef42c27840>
>>> inst.method  # inst.method è un metodo legato all'istanza
<bound method Test.method of <__main__.Test object at 0x7fef42c03f28>>
>>> Test.method(inst)  # possiamo chiamare Test.method e passare inst
self is: <__main__.Test object at 0x7fef42c03f28>
>>> inst.method()  # possiamo chiamare inst.method direttamente
self is: <__main__.Test object at 0x7fef42c03f28>

Entrambi i modi producono lo stesso risultato, ma normalmente viene usato il secondo modo (inst.method()): quando chiamiamo inst.method(), Python in realtà risale automaticamente alla classe di inst ed esegue Test.method(inst). Per questo è necessario che ogni metodo definito nella classe accetti self come primo argomento. Notare anche che self non ha niente di particolare o di diverso dagli altri argomenti (al contrario di altri linguaggi come Java che usa la keyword this per riferirsi all'istanza).

Dato che self si riferisce all'istanza, possiamo usarlo per accedere ad altri attributi e metodi definiti all'interno dello classe semplicemente facendo self.attribute o self.metodo().

Inizializzare istanze

Le classi supportano anche diversi metodi "speciali" che sono identificati dalla presenza di due underscore prima e dopo del nome. Questi metodi non vengono chiamati direttamente facendo inst.__metodo__, ma vengono in genere chiamati automaticamente in situazioni particolari

Uno di questi metodi speciali è __init__, chiamato automaticamente ogni volta che un'istanza viene creata:

>>> # definiamo una classe Test
>>> class Test:
...     # definiamo un __init__ che stampa un messaggio
...     def __init__(self):
...         print('New instance created!')
...
>>> Test()  # quando creiamo un'istanza, __init__ viene chiamato
New instance created!
<__main__.Test object at 0x7fef42c1eeb8>
>>> Test()  # quando creiamo un'istanza, __init__ viene chiamato
New instance created!
<__main__.Test object at 0x7fef3ff76390>

__init__ ha anche un'altra particolarità: gli argomenti passati durante la creazione dell'istanza vengono ricevuti da __init__. Questo ci permette di creare automaticamente istanze diverse in base agli argomenti passati:

>>> # definiamo una classe Dog
>>> class Dog:
...     # definiamo un __init__ che accetta un nome
...     def __init__(self, name):
...         # creiamo un attributo di istanza per il nome
...         self.name = name
...
>>> # creiamo due istanze di Dog
>>> rex = Dog('Rex')
>>> fido = Dog('Fido')
>>> rex.name  # verifichiamo che il nome della prima sia Rex
'Rex'
>>> fido.name  # e che il nome della seconda sia Fido
'Fido'

È anche importante notare che __init__ non equivale ai costruttori presente in altri linguaggi, dato che non crea l'istanza, ma la inizializza solamente.

Attributi

In queste ultime due lezioni abbiamo visto che gli attributi sono dei valori associati all'istanza (o alla classe) e abbiamo anche visto alcuni esempi di dichiarazione e uso di attributi.

Gli attributi si possono raggruppare in due categorie:

  • attributi di istanza;
  • attributi di classe.

Come si può intuire dai nomi, gli attributi di istanza sono legati a un'istanza specifica, mentre gli attributi di classe sono legati alla classe.

Gli attributi di istanza sono generalmente più comuni e si dichiarano facendo istanza.attributo = valore. Quando un attributo di istanza viene dichiarato all'interno di un metodo (ad esempio l'__init__), si usa self.attributo = valore, dato che il self si riferisce all'istanza:

>>> # definiamo una classe Dog
>>> class Dog:
...     # definiamo un __init__ che accetta un nome
...     def __init__(self, name):
...         # creiamo un attributo di istanza per il nome
...         self.name = name
...     # definiamo un metodo che accede al nome e lo stampa
...     def print_name(self):
...         print(self.name)
...
>>> # creiamo un'istanza di Dog
>>> dog = Dog('Rex')
>>> # accediamo all'attributo di istanza "name"
>>> dog.name
'Rex'
>>> # chiamiamo un metodo che stampa il nome
>>> dog.print_name()
Rex
>>> # cambiamo il valore dell'attributo dell'istanza
>>> dog.name = 'Fido'
>>> # verifichiamo che il nome è stato cambiato
>>> dog.name
'Fido'
>>> dog.print_name()
Fido

È anche possibile aggiungere o rimuovere attributi dalle istanze, ma generalmente sconsigliato, dato che è preferibile avere gli stessi attributi (anche se con valori diversi) su tutte le istanze della stessa classe:

>>> # creiamo un'istanza di Dog
>>> rex = Dog('Rex')
>>> # verifichiamo gli attributi e metodi dell'istanza
>>> # usando dir() (i metodi speciali sono stati omessi)
>>> dir(rex)
[..., 'name', 'print_name']
>>> # aggiungiamo un nuovo attributo all'istanza
>>> rex.job = 'police officer'
>>> # verifichiamo che l'attributo è stato aggiunto
>>> dir(rex)
[..., 'job', 'name', 'print_name']
>>> # accediamo al nuovo attributo
>>> rex.job
'police officer'
>>> # rimuoviamo l'attributo usando "del"
>>> del rex.job
>>> # verifichiamo che l'attributo è stato rimosso
>>> dir(rex)
[..., 'name', 'print_name']

Gli attributi di classe sono valori legati alla classe, che sono comuni e accessibili da tutte le istanze. Per dichiarare attributi di classe, esistono due modi: usando classe.attributo = valore o usando attributo = valore nel corpo della dichiarazione di una classe:

>>> # definiamo una classe Dog
>>> class Dog:
...     # definiamo un attributo di classe
...     scientific_name = 'Canis lupus familiaris'
...     # definiamo un __init__ che accetta un nome
...     def __init__(self, name):
...         # creiamo un attributo di istanza per il nome
...         self.name = name
...
>>> # creiamo due istanze di Dog
>>> rex = Dog('Rex')
>>> fido = Dog('Fido')
>>> # verifichiamo che ogni istanza ha un nome diverso
>>> rex.name
'Rex'
>>> fido.name
'Fido'
>>> # accediamo all'attributo di classe da Dog
>>> Dog.scientific_name
'Canis lupus familiaris'
>>> # accediamo all'attributo di classe dalle istanze
>>> rex.scientific_name
'Canis lupus familiaris'
>>> fido.scientific_name
'Canis lupus familiaris'
>>> # modifichiamo il valore dell'attributo di classe
>>> Dog.scientific_name = 'Canis lupus lupus'
>>> # verifichiamo il cambiamento dall'istanza
>>> rex.scientific_name
'Canis lupus lupus'

In questo esempio vediamo che il nome scientifico ('Canis lupus familiaris'), comune a tutte le istanze di Dog, viene dichiarato durante la definizione della classe, ed è accessibile sia dalle istanze che dalla classe stessa. Ogni istanza ha poi un nome univoco (es. 'Rex' e 'Fido'), che viene definito nell'__init__ e che è accessibile solamente dalle istanze.

Ti consigliamo anche