Ricerca…


Metodi Magici / Dunder

I metodi di Magic (chiamati anche dunder come abbreviazione per double-underscore) in Python hanno lo stesso scopo di sovraccaricare gli operatori in altre lingue. Consentono a una classe di definire il proprio comportamento quando viene utilizzata come operando in espressioni unate o binarie. Servono anche come implementazioni chiamate da alcune funzioni integrate.

Considerare questa implementazione di vettori bidimensionali.

import math

class Vector(object):
    # instantiation
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # unary negation (-v)
    def __neg__(self):
        return Vector(-self.x, -self.y)

    # addition (v + u)
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # subtraction (v - u)
    def __sub__(self, other):
        return self + (-other)

    # equality (v == u)
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    # abs(v)
    def __abs__(self):
        return math.hypot(self.x, self.y)

    # str(v)
    def __str__(self):
        return '<{0.x}, {0.y}>'.format(self)

    # repr(v)
    def __repr__(self):
        return 'Vector({0.x}, {0.y})'.format(self)

Ora è possibile utilizzare naturalmente le istanze della classe Vector in varie espressioni.

v = Vector(1, 4)
u = Vector(2, 0)

u + v           # Vector(3, 4)
print(u + v)    # "<3, 4>" (implicit string conversion)
u - v           # Vector(1, -4)
u == v          # False
u + v == v + u  # True
abs(u + v)      # 5.0

Tipi di contenitori e di sequenza

È possibile emulare tipi di contenitori che supportano l'accesso ai valori per chiave o indice.

Considera questa ingenua implementazione di una lista sparsa, che memorizza solo i suoi elementi diversi da zero per conservare la memoria.

class sparselist(object):
    def __init__(self, size):
        self.size = size
        self.data = {}
    
    # l[index]
    def __getitem__(self, index):
        if index < 0:
            index += self.size
        if index >= self.size:
            raise IndexError(index)
        try:
            return self.data[index]
        except KeyError:
            return 0.0

    # l[index] = value
    def __setitem__(self, index, value):
        self.data[index] = value

    # del l[index]
    def __delitem__(self, index):
        if index in self.data:
            del self.data[index]

    # value in l
    def __contains__(self, value):
        return value == 0.0 or value in self.data.values()

    # len(l)
    def __len__(self):
        return self.size

    # for value in l: ...
    def __iter__(self):
        return (self[i] for i in range(self.size)) # use xrange for python2

Quindi, possiamo usare uno sparselist come una list normale.

l = sparselist(10 ** 6)  # list with 1 million elements
0 in l                   # True
10 in l                  # False

l[12345] = 10            
10 in l                  # True
l[12345]                 # 10

for v in l:
    pass  # 0, 0, 0, ... 10, 0, 0 ... 0

Tipi chiamabili

class adder(object):
    def __init__(self, first):
        self.first = first

    # a(...)
    def __call__(self, second):
        return self.first + second

add2 = adder(2)
add2(1)  # 3
add2(2)  # 4

Gestione del comportamento non implementato

Se la classe non implementa uno specifico operatore sovraccarico per i tipi di argomento forniti, dovrebbe return NotImplemented ( notare che questa è una costante speciale , non la stessa di NotImplementedError ). Ciò consentirà a Python di tornare a provare altri metodi per far funzionare l'operazione:

Quando viene restituito NotImplemented , l'interprete proverà l'operazione riflessa sull'altro tipo, o qualche altro fallback, a seconda dell'operatore. Se tutte le operazioni tentate restituiscono NotImplemented , l'interprete genererà un'eccezione appropriata.

Ad esempio, dato x + y , se x.__add__(y) restituisce non implementato, viene invece tentato y.__radd__(x) .

class NotAddable(object):

    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return NotImplemented


class Addable(NotAddable):

    def __add__(self, other):
        return Addable(self.value + other.value)

    __radd__ = __add__

Poiché questo è il metodo riflesso , dobbiamo implementare __add__ e __radd__ per ottenere il comportamento previsto in tutti i casi; fortunatamente, dato che entrambi stanno facendo la stessa cosa in questo semplice esempio, possiamo prendere una scorciatoia.

In uso:

>>> x = NotAddable(1)
>>> y = Addable(2)
>>> x + x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NotAddable' and 'NotAddable'
>>> y + y
<so.Addable object at 0x1095974d0>
>>> z = x + y
>>> z
<so.Addable object at 0x109597510>
>>> z.value
3

Sovraccarico dell'operatore

Di seguito sono riportati gli operatori che possono essere sovraccaricati in classi, insieme alle definizioni dei metodi richieste e un esempio dell'operatore in uso all'interno di un'espressione.

NB L'uso other come nome variabile non è obbligatorio, ma è considerato la norma.

Operatore Metodo Espressione
+ Aggiunta __add__(self, other) a1 + a2
- Sottrazione __sub__(self, other) a1 - a2
* Moltiplicazione __mul__(self, other) a1 * a2
@ Moltiplicazione della matrice __matmul__(self, other) a1 @ a2 ( Python 3.5 )
/ Divisione __div__(self, other) a1 / a2 ( solo Python 2 )
/ Divisione __truediv__(self, other) a1 / a2 ( Python 3 )
// Floor Division __floordiv__(self, other) a1 // a2
% Modulo / resto __mod__(self, other) a1 % a2
** Potenza __pow__(self, other[, modulo]) a1 ** a2
<< Bitwise Left Shift __lshift__(self, other) a1 << a2
>> Spostamento a destra bit a bit __rshift__(self, other) a1 >> a2
& Bitwise AND __and__(self, other) a1 & a2
^ XOR bit a bit __xor__(self, other) a1 ^ a2
| (OR bit a bit) __or__(self, other) a1 | a2
- Negazione (aritmetica) __neg__(self) -a1
+ Positivo __pos__(self) +a1
~ Bitwise NOT __invert__(self) ~a1
< Meno di __lt__(self, other) a1 < a2
<= Minore o uguale a __le__(self, other) a1 <= a2
== Uguale a __eq__(self, other) a1 == a2
!= Non uguale a __ne__(self, other) a1 != a2
> Maggiore di __gt__(self, other) a1 > a2
>= Maggiore di o uguale a __ge__(self, other) a1 >= a2
[index] Operatore indice __getitem__(self, index) a1[index]
in operatore In __contains__(self, other) a2 in a1
(*args, ...) Chiamare __call__(self, *args, **kwargs) a1(*args, **kwargs)

Il parametro opzionale modulo per __pow__ viene utilizzato solo dalla funzione integrata di pow .


Ciascuno dei metodi corrispondenti a un operatore binario ha un metodo "giusto" corrispondente che inizia con __r , ad esempio __radd__ :

class A:
    def __init__(self, a):
        self.a = a
    def __add__(self, other):
        return self.a + other
    def __radd__(self, other):
        print("radd")
        return other + self.a

A(1) + 2  # Out:  3
2 + A(1)  # prints radd. Out: 3

così come una corrispondente versione inplace, a partire da __i :

class B:
    def __init__(self, b):
        self.b = b
    def __iadd__(self, other):
        self.b += other
        print("iadd")
        return self

b = B(2)
b.b       # Out: 2
b += 1    # prints iadd
b.b       # Out: 3

Poiché non c'è nulla di speciale in questi metodi, molte altre parti del linguaggio, parti della libreria standard e persino moduli di terze parti aggiungono metodi magici da soli, come metodi per eseguire il cast di un oggetto su un tipo o controllare le proprietà dell'oggetto. Ad esempio, la funzione builtin str() chiama il metodo __str__ dell'oggetto, se esiste. Alcuni di questi usi sono elencati di seguito.

Funzione Metodo Espressione
Trasmissione a int __int__(self) int(a1)
Funzione assoluta __abs__(self) abs(a1)
Trasmissione a str __str__(self) str(a1)
Casting to unicode __unicode__(self) unicode(a1) (solo Python 2)
Rappresentazione delle stringhe __repr__(self) repr(a1)
Casting to bool __nonzero__(self) bool(a1)
Formattazione delle stringhe __format__(self, formatstr) "Hi {:abc}".format(a1)
hashing __hash__(self) hash(a1)
Lunghezza __len__(self) len(a1)
Reversed __reversed__(self) reversed(a1)
Pavimento __floor__(self) math.floor(a1)
Soffitto __ceil__(self) math.ceil(a1)

Esistono anche i metodi speciali __enter__ e __exit__ per i gestori di contesto e molti altri.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow