Nelle lezioni precedenti abbiamo visto che esistono diversi metodi "speciali", indicati dalla presenza di due underscore prima e dopo il nome (ad esempio, __metodo__
). Questi metodi non vengono chiamati direttamente dal programmatore, ma vengono invocati da Python in situazioni particolari. Parlando delle classi, ad esempio, abbiamo incontrato il metodo speciale __init__
, invocato automaticamente durante la creazione delle istanze. In questa lezione vedremo altri metodi speciali e come possono essi possano essere utili in vari contesti.
__str__ e __repr__
Oltre all'__init__
, ci sono altri due metodi speciali che vengono comunemente aggiunti quando si definisce una nuova classe: __str__
e __repr__
. Questi due metodi vengono invocati automaticamente quando eseguiamo str(istanza)
e repr(istanza)
, o quando chiamiamo funzioni che eseguono queste operazioni (ad esempio: print(istanza)
).
Entrambi i metodi devono restituire una stringa: la differenza è che __str__
restituisce un valore utile all'utente, mentre __repr__
un valore utile allo sviluppatore:
>>> # definiamo una classe Person
>>> class Person:
... # definiamo un __init__ che assegna nome e cognome all'istanza
... def __init__(self, name, surname):
... self.name = name
... self.surname = surname
... # definiamo uno __str__ che restituisce nome e cognome
... def __str__(self):
... return '{} {}'.format(self.name, self.surname)
... # definiamo uno __repr__ che restituisce il tipo dell'istanza
... def __repr__(self):
... return '<Person object ({} {})>'.format(self.name, self.surname)
...
>>> # creiamo un'istanza di Person
>>> p = Person('Ezio', 'Melotti')
>>> # l'interprete stampa automaticamente il repr() dell'oggetto
>>> # e il metodo p.__repr__() viene invocato
>>> p
'<Person object (Ezio Melotti)>'
>>> repr(p)
'<Person object (Ezio Melotti)>'
>>> # se usiamo str(), print(), o format(), p.__str__() viene chiamato
>>> # automaticamente e il nome completo viene restituito
>>> str(p)
'Ezio Melotti'
>>> print(p)
Ezio Melotti
>>> 'Welcome {}!'.format(p)
'Welcome Ezio Melotti!'
Da questo esempio possiamo vedere che il __repr__
contiene informazioni utili allo sviluppatore, come il il tipo dell'oggetto e il valore degli attributi name
e surname
, mentre il valore restituito da __str__
include solo il nome e il cognome, e può essere usato direttamente in un'interfaccia utente o in messaggi generati mediante la funzione print
.
Il valore restituito da __repr__
può contenere più o meno informazioni, ma in genere è bene che rispetti una delle seguenti due forme:
<Classe object ...>
: il nome della classe seguita da informazioni aggiuntive (ad esempio il valore di alcuni attributi), il tutto racchiuso tra<...>
(es.
).
<Person object: name='Ezio' surname='Melotti'>Classe(arg1, arg2, ..., argN)
: l'espressione usata per creare l'istanza, in grado di dirci il nome della classe e il valore degli attributi (es.Person('Ezio', 'Melotti')
).
__bool__ e __len__
Il metodo speciale __bool__
può essere usato per definire se un oggetto è vero o falso, mentre il metodo __len__
può ritornare la lunghezza (o il numero di elementi) di un oggetto.
Se __bool__
non è definito, Python può usare il risultato di __len__
per determinare se un oggetto è vero o falso (una lunghezza diversa da 0 è considerata vera). Se anche __len__
non è definito, l'oggetto è considerato vero.
>>> # definiamo una classe Team
>>> class Team:
... # definiamo un __init__ che assegna i membri all'istanza
... def __init__(self, members):
... self.members = members
... # definiamo un __bool__ che restituisce False se il
... # team ha 0 membri, altrimenti True
... def __bool__(self):
... return len(self.members) > 0
... # definiamo un __len__ che restituisce il numero di membri
... def __len__(self):
... return len(self.members)
... # definiamo un __repr__ che restituisce il tipo dell'oggetto
... # e i nomi dei membri del team
... def __repr__(self):
... names = ', '.join([p.name for p in self.members])
... return '<Team object [{}]>'.format(names)
...
>>> # creiamo un'istanza di Team con 3 membri
>>> t1 = Team([Person('Guido', 'van Rossum'),
... Person('Alex', 'Martelli'),
... Person('Ezio', 'Melotti'),])
>>> # verifichiamo il repr dell'oggetto
>>> t1
<Team object [Guido, Alex, Ezio]>
>>> # verifichiamo che il team ha 3 membri
>>> t1.members
[<Person object (Guido van Rossum)>,
<Person object (Alex Martelli)>,
<Person object (Ezio Melotti)>]
>>> # verifichiamo che la lunghezza del team è 3
>>> len(t1)
3
>>> # verifichiamo che questo team è considerato "vero"
>>> bool(t1)
True
>>>
>>> # creiamo un'altra istanza di Team con 0 membri
>>> t2 = Team([])
>>> # verifichiamo il repr dell'oggetto
>>> t2
<Team object []>
>>> # verifichiamo che il team ha 0 membri
>>> t2.members
[]
>>> # verifichiamo che la lunghezza del team è 0
>>> len(t2)
0
>>> # verifichiamo che questo team è considerato "falso"
>>> bool(t2)
False
Nell'esempio, abbiamo definito una classe Team
che include una lista di membri. Quando usiamo len(istanza)
, viene automaticamente invocato il metodo Team.__len__()
, che ci restituisce il numero di membri nel team. Quando usiamo bool(istanza)
o if istanza: ...
, viene invece invocato il metodo Team.__bool__()
, che ci restituisce True
se il team ha almeno un membro, altrimenti False
.