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

Overloading degli operatori

Su Python è possibile ridefinire il comportamento degli operatori logici e aritmetici di base, implementando opportunamente alcuni metodi speciali.
Su Python è possibile ridefinire il comportamento degli operatori logici e aritmetici di base, implementando opportunamente alcuni metodi speciali.
Link copiato negli appunti

Fare l'overloading degli operatori significa definire (o ridefinire) il comportamento di un operatore durante l'interazione con un'istanza di una classe che abbiamo creato in precedenza. Questo ci permette di definire cosa succede quando, ad esempio, utilizziamo una sintassi del tipo istanza1 + istanza2.

Nelle lezioni precedenti, abbiamo visto diversi tipi di operatori: aritmetici (+, -, *, /, //, %), di confronto (==, !=, <, <=, >, >=), binari (<<, >>, &, |, ^, ~), di contenimento (in e not in), di indexing (oggetto[indice]), di accesso a attributi (oggetto.attributo).

Per ognuno di questi operatori esiste un corrispondente metodo speciale, che può essere definito per specificare il risultato dell'operazione. Per diversi operatori esistono anche due tipi aggiuntivi di metodi speciali, la versione speculare e quella in place. Ad esempio, l'operatore + ha tre metodi speciali:

  • __add__: quando eseguiamo istanza + valore, viene in realtà eseguito il metodo istanza.__add__(valore);
  • __radd__: quando eseguiamo valore + istanza, e il valore non definisce un metodo __add__ compatibile con la nostra istanza, viene eseguito il metodo istanza.__radd__(valore);
  • __iadd__: quando eseguiamo istanza += valore, viene eseguito istanza.__iadd__(valore), permettendoci di modificare l'istanza in place.

Vediamo alcuni esempi di overloading degli operatori:

>>> # definiamo una classe Team
>>> class Team:
...     # definiamo un __init__ che assegna i membri all'istanza
...     def __init__(self, members):
...         self.members = 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)
...     # definiamo un __contains__ che restituisce True se un membro
...     # fa parte del team, altrimenti False
...     def __contains__(self, other):
...         return other in self.members
...     # definiamo un __add__ che restituisce un nuovo team creato
...     # dall'aggiunta di una nuova persona o dall'unione di 2 team
...     def __add__(self, other):
...         if isinstance(other, Person):
...             return Team(self.members + [other])
...         elif isinstance(other, Team):
...             return Team(self.members + other.members)
...         else:
...             raise TypeError("Can't add Team with {!r}.".format(other))
...     # definiamo un __radd__ che è uguale ad __add__, visto che
...     # l'addizione è un'operazione commutativa
...     __radd__ = __add__
...     # definiamo un __iadd__ che modifica il team aggiungendo una
...     # nuova persona o i membri di un altro team al team corrente
...     def __iadd__(self, other):
...         if isinstance(other, Person):
...             self.members.append(other)
...             return self
...         elif isinstance(other, Team):
...             self.members.extend(other.members)
...             return self
...         else:
...             raise TypeError("Can't add {!r} to the team.".format(other))
...
>>>
>>> # creiamo 4 istanze di Person
>>> guido = Person('Guido', 'van Rossum')
>>> tim = Person('Tim', 'Peters')
>>> alex = Person('Alex', 'Martelli')
>>> ezio = Person('Ezio', 'Melotti')
>>>
>>> # creiamo 2 team da 2 persone per team
>>> t1 = Team([guido, tim])
>>> t2 = Team([alex, ezio])
>>>
>>> # verifichiamo i membri dei 2 team
>>> t1
<Team object [Guido, Tim]>
>>> t2
<Team object [Alex, Ezio]>
>>>
>>> # verifichiamo l'overloading dell'operatore in
>>> guido in t1
True
>>> ezio in t1
False
>>> ezio not in t1
True
>>>
>>> # verifichiamo l'overloading dell'operatore + (__add__)
>>> # sommando un'istanza di Team con una di Person
>>> t1 + ezio
<Team object [Guido, Tim, Ezio]>
>>> # verifichiamo che l'operazione ha restituito
>>> # un nuovo team, e che t1 non è cambiato
>>> t1
<Team object [Guido, Tim]>
>>>
>>> # verifichiamo l'overloading dell'operatore + (__radd__)
>>> # sommando un'istanza di Person con una di Team
>>> ezio + t1
<Team object [Guido, Tim, Ezio]>
>>>
>>> # verifichiamo l'overloading dell'operatore + (__add__)
>>> # sommando due istanze di Team
>>> t1 + t2
<Team object [Guido, Tim, Alex, Ezio]>
>>> t2 + t1
<Team object [Alex, Ezio, Guido, Tim]>
>>>
>>> # verifichiamo che t1 contiene 2 membri
>>> t1
<Team object [Guido, Tim]>
>>> # verifichiamo l'overloading dell'operatore += (__iadd__)
>>> # aggiungendo un'istanza di Person al Team t1
>>> t1 += ezio
>>> # verifichiamo che t1 è stato modificato
>>> t1
<Team object [Guido, Tim, Ezio]>
>>>
>>> # creiamo altre 2 istanze di Team
>>> t3 = Team([alex, tim])
>>> t4 = Team([guido, ezio])
>>> # verifichiamo che t3 contiene 2 membri
>>> t3
<Team object [Alex, Tim]>
>>> # verifichiamo l'overloading dell'operatore += (__iadd__)
>>> # aggiungendo un'istanza di Team al Team t3
>>> t3 += t4
>>> # verifichiamo che t3 è stato modificato
>>> t3
<Team object [Alex, Tim, Guido, Ezio]>
>>>
>>> # verifichiamo che aggiungere un tipo incompatibile
>>> # ci restituisce un TypeError
>>> t3 + 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in __add__
TypeError: Can't add Team with 5.

In questo esempio abbiamo definito una classe Team che implementa diversi metodi speciali: __init__, __repr__, __contains__, __add__, __radd__, __iadd__. Questi metodi ci hanno permesso di definire il comportamento degli operatori in, not in, +, += e di verificare i diversi risultati che otteniamo combinandoli con istanze della classe Team e della classe Person, definita in un esempio precedente.

Riassunto dei metodi speciali

Le tabelle seguenti riassumono gli operatori più comunemente usati e i loro metodi speciali.

Oltre al corrispondente metodo speciale (es. __add__), gli operatori aritmetici hanno anche la versione speculare (es. __radd__) e quella in place (es. __iadd__):

Operatore Descrizione Metodi speciali
+ addizione __add__, __radd__, __iadd__
sottrazione __sub__, __rsub__, __isub__
* moltiplicazione __mul__, __rmul__, __imul__
/ divisione __truediv__, __rtruediv__, __itruediv__
// divisione intera __floordiv__, __rfloordiv__, __ifloordiv__
% modulo (resto della divisione) __mod__, __rmod__, __imod__

Ogni operatore di confronto ha solo un corrispondente metodo speciale:

Operatore Descrizione Metodo speciali
== uguale a __eq__
!= diverso da __ne__
< minore di __lt__
<= minore o uguale a __le__
> maggiore di __gt__
>= maggiore o uguale a __ge__

Così come gli operatori aritmetici, anche gli operatori binari hanno una versione speculare e una versione in place:

Operatore Descrizione Metodi speciali
x << n esegue uno shift a sinistra di n posizioni dei bit di x __lshift__, __rlshift__, __ilshift__
x >> n esegue uno shift a destra di n posizioni dei bit di x __rshift__, __rrshift__, __irshift__
x & y esegue un and tra i bit di x e di y __and__, __rand__, __iand__
x | y esegue un or tra i bit di x e di y __or__, __ror__, __ior__
x ^ y esegue un or esclusivo tra i bit di x e di y __xor__, __rxor__, __ixor__

Esistono anche metodi speciali per determinare il comportamento di un'oggetto durante l'accesso, l'assegnamento, e la rimozione di elementi o attributi:

Operatore Descrizione Metodo speciali
object[item] accesso a un elemento __getitem__
object[item] = value assegnamento a un elemento __setitem__
del object[item] rimozione di un elemento __delitem__
object.attr accesso a un attributo __getattr__
object.attr = value assegnamento a un attributo __setattr__
del object.attr rimozione di un attributo __delattr__

Esistono infine altri metodi speciali meno comuni, che per brevità non sono inclusi in questa guida, ma che si possono trovare nella documentazione ufficiale sui metodi speciali.

È inoltre importante notare che esistono alcuni operatori su cui non è possibile effettuare l'overloading, in particolare l'operatore di assegnamento (=), e gli operatori booleani (and, or, not).


Ti consigliamo anche