Overloading delle operazioni matematiche in Python
Python ti permette di implementare e personalizzare le operazioni matematiche all'interno di una classe tramite l'overloading.
Cos'è l'overloading? L'overloading (o sovraccarico) è la capacità di definire più versioni di un metodo o operatore con lo stesso nome ma con differenti tipi o numeri di parametri. In Python, l'overloading è spesso implementato utilizzando i metodi magici per personalizzare il comportamento degli operatori standard quando applicati agli oggetti delle classi definite dall'utente.
In altre parole, ti consente di usare gli operatori matematici come +, -, *, ecc. e indicare come devono comportarsi quando sono usati sugli oggetti di una classe.
Ti faccio un esempio pratico.
L'operatore di assegnazione e incremento += è usato per aggiungere un valore a quello già presente in una variabile numerica.
Ad esempio, assegna il numero 1 alla variabile num.
num=1
Poi usa l'operatore += per aggiungere 2 al valore già esistente.
num+=2
Il risultato finale è 1+2=3
print(num)
3
L'operatore di assegnazione e incremento += funziona con i valori numerici ma non funziona se vuoi usarlo per sommare i vettori.
Puoi però implementare l'operatore += all'interno di una classe definendo il metodo speciale __iadd__() che viene invocato quando si utilizza l'operatore += su un'istanza della classe.
L'uso di __iadd__ ti permette di definire anche un comportamento personalizzato per l'operatore += in una classe.
Ad esempio, crea una classe Vector che rappresenta un vettore nello spazio bidimensionale (2D).
Poi implementa il metodo __iadd__() per sommare un altro vettore o una coppia di coordinate (x, y) al vettore corrente.
class Vector:
def __init__(self, x: float = 0.0, y: float = 0.0):
self.x = x
self.y = y
def __iadd__(self, other):
if isinstance(other, Vector):
# Se other è un oggetto di tipo Vector, somma le coordinate corrispondenti
self.x += other.x
self.y += other.y
elif isinstance(other, (tuple, list)) and len(other) == 2:
# Se other è una tupla o una lista con due elementi, somma i valori di x e y
self.x += other[0]
self.y += other[1]
else:
raise TypeError("Il tipo di other deve essere Vector o una tupla/lista di due numeri")
# Restituisce l'oggetto corrente per supportare il chaining
return self
def __repr__(self):
return f"Vector({self.x}, {self.y})"
È importante restituire self alla fine del metodo per garantire che l'operazione avvenga in-place, modificando l'istanza corrente. Questa tecnica è utile soprattutto quando si lavora con oggetti mutabili.
Definisci due oggetti v1 e v2 di tipo Vector
v1 = Vector(1, 2)
v2 = Vector(3, 4)
Poi usa l'operatore di assegnazione e incremento += per sommare il secondo vettore al primo vettore
v1 += v2
Ora stampa il risultato
print(v1)
Vector(4, 6)
Al primo vettore è stato aggiunto il secondo.
$$ \vec{v}_1 + \vec{v}_2 = \begin{pmatrix} 1 \\ 2 \end{pmatrix} + \begin{pmatrix} 3 \\ 4 \end{pmatrix} = \begin{pmatrix} 1+3 \\ 2+4 \end{pmatrix} = \begin{pmatrix} 4 \\ 6 \end{pmatrix} $$
Puoi anche sommare un vettore e una tupla (x, y)
v1 += (1, 1)
print(v1)
Vector(5, 7)
Infine, puoi sommare un vettore e una lista (x, y)
v1 += [2, 3]
print(v1)
Vector(7, 10)
Ricorda però che l'operazione non è commutativa.
Il metodo __iadd__() viene invocato solo se il primo operando è un oggetto della classe Vector dove hai implementato il metodo speciale.
Ad esempio, se il primo operando è una tupla, l'operazione solleva un'eccezione.
v3=(1,1)
v3 += v1
Traceback (most recent call last):
TypeError: can only concatenate tuple (not "Vector") to tuple
Usando questa tecnica puoi implementare e personalizzare tutti gli operatori matematici.
Ecco una lista completa dei metodi speciali che puoi utilizzare nel linguaggio Python
Metodo magico | Significato |
---|---|
__add__ | + |
__sub__ | - |
__mul__ | * |
__floordiv__ | // |
__div__ | / |
__truediv__ | / |
__mod__ | % |
__divmod__ | divmod(a, b) |
__pow__ | ** pow(a, power, modulo=None) |
__lshift__ | << |
__rshift__ | >> |
__and__ | & |
__xor__ | ^ |
__or__ | | |
__neg__ | - |
__pos__ | + |
__abs__ | abs(a) |
__invert__ | ~ |
Le operazioni commutative
Un aspetto da ricordare è che l'overloading delle operazioni aritmetiche non è commutativo.
Ad esempio, implementa il prodotto di un vettore per uno scalare nella class Vector tramite il metodo __mul__()
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return
def __repr__(self):
return f"Vector({self.x}, {self.y})"
Il sovraccarico del metodo __mul__() ti consente di utilizzare l'operatore * per moltiplicare un vettore per un numero scalare.
v = Vector(1, 2)
print(v * 3)
Quando invochi il metodo nell'operazione v*3, Python riconosce che il primo operando (v) è un vettore mentre il secondo operando (3) è un numero scalare.
Quindi richiama l'overloading del metodo __mul__() nella classe Vector e restituisce il risultato corretto.
Vector(3, 6)
Se però digiti 3*v Python non trova un vettore al primo operando dell'operazione di moltiplicazione.
In questo caso Python non chiama l'overloading di __mul__() restituendo un errore.
v = Vector(1, 2)
print(3 * v)
Traceback (most recent call last):
TypeError: unsupported operand type(s) for *: 'int' and 'Vector'
Per rendere la moltiplicazione commutativa devi implementare anche il metodo "__rmul__()" nella classe Vector.
Questo metodo inverte l'ordine degli operandi da destra verso sinistra.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return
def __rmul__(self, scalar):
# Si utilizza lo stesso metodo per rendere il prodotto commutativo
return self.__mul__(scalar)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
Adesso il prodotto di un vettore per uno scalare funziona sia se il vettore è il primo operando, sia se è il secondo operando.
v = Vector(1, 2)
print(v * 3)
print(3 * v)
Vector(3, 6)
Vector(3, 6)
Utilizzando questa tecnica puoi rendere commutativa l'operazione di overloading dell'addizione usando il metodo "__radd__()".
Nota. Ricorda però che i metodi inversi (es. __radd__(), __rmul__(), ecc. ) esistono solo per alcune operazioni matematiche e non per tutte. Ad esempio, il metodo __iadd__() non ha un metodo inverso di default ma va costruito.