Lezioni del modulo (3/4)
Metodi speciali (dunder)
I metodi speciali (chiamati dunder methods perché iniziano e
finiscono con doppio underscore: __nome__) sono i ganci che permettono ai
tuoi oggetti di integrarsi con il linguaggio: rappresentazione testuale,
confronto con ==, supporto a len(), in, operatori, ecc.
Ne hai già usato uno: __init__.
__str__ e __repr__
Controllano cosa appare con print() e nel REPL.
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Punto({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Punto(3, 4)
str(p) # '(3, 4)' — per umani
repr(p) # 'Punto(3, 4)' — per debugger / dev
print(p) # '(3, 4)'
p # Punto(3, 4) in REPL → repr
[p, p] # [Punto(3, 4), Punto(3, 4)] — sempre repr nei contenitoriSe definisci solo __repr__, Python lo usa anche come fallback per __str__.
__eq__: ridefinire ==
Di default a == b confronta gli identificatori degli oggetti (is),
non i contenuti. Per il confronto strutturale, definisci __eq__:
class Punto:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
if not isinstance(other, Punto):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
Punto(1, 2) == Punto(1, 2) # True
Punto(1, 2) == Punto(3, 4) # FalseRestituire NotImplemented (parola chiave speciale, non False!) quando i
tipi non sono confrontabili è la prassi corretta: Python prova allora con
l'__eq__ dell'altro operando.
__len__: supporto a len()
class Cesto:
def __init__(self):
self.frutti = []
def __len__(self):
return len(self.frutti)
c = Cesto()
c.frutti.append("mela")
len(c) # 1
bool(c) # True (len > 0)Definendo __len__, ottieni anche il boolean "gratis": un oggetto con
len() == 0 diventa falsy.
Altri dunder utili (cenni)
__add__(self, other)→ ridefinisce+__lt__,__le__,__gt__,__ge__→ operatori di confronto__contains__(self, item)→ supportaitem in self__iter__+__next__→ rende l'oggetto iterabile__getitem__(self, key)→ supportaself[key]__hash__→ necessario per essere chiave di dict / elemento di set
Dunder repr vs str
C'è una differenza sottile tra questi due metodi:
__str__è pensato per essere leggibile per l'utente finale (prosa amichevole).__repr__è pensato per essere univoco ed esplicito per lo sviluppatore (dovrebbe assomigliare al codice Python necessario a ricreare l'oggetto). Se__str__non è definito, Python userà__repr__come fallback.
Dunder repr vs str
C'è una differenza sottile tra questi due metodi:
__str__è pensato per essere leggibile per l'utente finale (prosa amichevole).__repr__è pensato per essere univoco ed esplicito per lo sviluppatore (dovrebbe assomigliare al codice Python necessario a ricreare l'oggetto). Se__str__non è definito, Python userà__repr__come fallback.
Prova tu
Definisci `Money` con __init__(self, amount, currency) e __str__ che restituisce `f'{amount} {currency}'`. Crea `s = Money(42, 'EUR')` e valuta `str(s)`.
Mostra suggerimento
f-string nel __str__.
Soluzione disponibile dopo 3 tentativi
Esercizio di ripasso
Definisci `Pair` con __init__(self, a, b) e __eq__ basato sul confronto strutturale di (a, b). Valuta `Pair(1, 2) == Pair(1, 2)`.
Mostra suggerimento
Confronta le tuple (self.a, self.b) == (other.a, other.b).
Soluzione disponibile dopo 3 tentativi
Sfida aggiuntiva
Definisci una classe `Book` con costruttore che accetta `title` ed `author`. Definisci il metodo speciale `__str__(self)` affinché restituisca la stringa `"Titolo by Autore"` (es. `'1984 by George Orwell'`). Istanzia un libro ed evalua `str(book)`.
Mostra suggerimento
Usa f"{self.title} by {self.author}" all'interno di def __str__(self):.
Soluzione disponibile dopo 3 tentativi