Python avanzato: Cosa sono i metodi magici?

Questo articolo evidenzia i metodi speciali di Python che ogni programmatore Python deve conoscere

Farhad Malik

Follow

16 maggio, 2020 – 14 min read

I metodi magici ci aiutano ad arricchire le nostre applicazioni. Aggiungono indirettamente magia al nostro codice Python. Questo è un argomento di livello avanzato per gli sviluppatori Python e lo raccomando a tutti coloro che usano o intendono usare il linguaggio di programmazione Python.

I metodi magici ci danno più controllo su come la nostra applicazione si comporta.

Questo articolo ha lo scopo di spiegare cosa sono i metodi magici e come possono essere usati per costruire applicazioni Python. Fornirà una panoramica dei metodi magici più usati per una serie di tipi di dati.

Lo scopo di delineare i metodi magici chiave è quello di capire se vogliamo sovrascrivere questi metodi nelle nostre classi personalizzate per arricchire le applicazioni.

I metodi magici rendono il linguaggio di programmazione Python estremamente potente

Foto di Rodion Kutsaev su Unsplash

Quali sono i metodi magici di Python?

I metodi magici di Python sono anche conosciuti come metodi speciali o metodi dunder. Sono circondati da doppi underscore, ad esempio __init__().

Un oggetto può avere un certo numero di metodi magici.

Ricordate che tutto in Python è un oggetto, inclusa una variabile/funzione/classe ecc. Gli oggetti sono l’astrazione di Python per i dati.

I metodi magici sono usati per costruire e inizializzare nuovi oggetti, ci aiutano a recuperare un oggetto come dizionario, sono usati per cancellare un oggetto tra le altre operazioni. Sono usati quando viene invocato l’operatore +, o anche quando vogliamo rappresentare un oggetto come una stringa.

Anche se ogni metodo in Python è pubblico, la convenzione di codifica è di circondare tutti i metodi privati da doppi underscore __<metodo>__()

Questo implica che i metodi magici sono intesi come metodi privati. Significa anche che il chiamante di un oggetto non dovrebbe invocare il metodo direttamente poiché il metodo è destinato ad essere invocato internamente dalla classe che ha il metodo magico.

Possiamo sovrascrivere i metodi magici per fornire la nostra funzionalità personalizzata.

Foto di Cristian Escobar su Unsplash

Vi spiegherò i concetti dei metodi magici creando una classe personalizzata e poi fornirò una panoramica dei metodi magici più importanti in un intero, una stringa, una lista e un dizionario.

Proseguendo lungo l’articolo, si comincerà a costruire un quadro molto più chiaro del perché i metodi magici esistono e come usarli.

Se vuoi capire il linguaggio di programmazione Python dal principiante al livello avanzato allora ti consiglio vivamente l’articolo qui sotto:

Inizierò l’argomento dei metodi magici creando una classe personalizzata e poi spiegherò come si usano i metodi magici.

Il caso d’uso per capire i metodi magici

Nello snippet sottostante, ho creato una classe chiamata Human e poi ho istanziato un’istanza della classe Human.

Questo frammento di codice ci aiuterà a capire i metodi magici.

Nota, l’id dell’oggetto human è di tipo intero, l’attributo name è di tipo stringa, la proprietà addresses è di tipo lista e la proprietà maps è di tipo dizionario.

Ho raggruppato i metodi magici in varie sezioni di tipo dati per facilitare la lettura. Tuttavia, gli stessi metodi magici si trovano in diversi tipi di dati.

I metodi magici possono essere sovrascritti per arricchire la funzionalità e creare la logica personalizzata che meglio si adatta alle esigenze aziendali.

Class:

Comprendiamo i metodi magici che sono associati all’oggetto human. Se eseguo il metodo dir(human), elencherà tutte le funzioni e i nomi degli attributi dell’oggetto human.

Ci sono in totale 29 metodi/attributi associati all’oggetto human. Tra questi, 26 sono i metodi magici.

Questo è un numero abbastanza grande di metodi speciali. Questi metodi sono ereditati dal tipo base della classe Human. Quindi, sono i metodi predefiniti che possiamo usare/sovrascrivere per arricchire le classi.

La prossima parte della sezione spiegherà i metodi magici chiave.

__delattr__

Questo metodo è chiamato quando tentiamo di cancellare un attributo da una classe.

Possiamo sovrascrivere la funzionalità implementando il metodo nella classe Human:

def __delattr__(self, item):
print('Deleting attribute ' + item)
return object.__delattr__(self, item)

Ora, ogni volta che tentiamo di cancellare un attributo, verrà visualizzato il messaggio: Deleting attribute

C’è anche il metodo __setattr__() per assegnare un valore a un attributo e __getattr__() per ottenere un valore dall’attributo.

Un caso d’uso di__delattr__() può essere quello di impedire che particolari attributi di un oggetto vengano cancellati o quando vogliamo eseguire azioni specifiche quando un particolare attributo viene cancellato.

__dict__

Questo metodo ritorna un dizionario che rappresenta l’oggetto. Le chiavi del dizionario sono gli attributi dell’oggetto e i valori sono i valori degli attributi.

Come istanza:

human = Human(2, 'Malik').__dict__
print(human)

Il codice sopra restituisce:

{‘id’: 2, ‘name’: ‘Malik’, ‘addresses’: , ‘mappe’: {}}

__dir__

Possiamo sovrascrivere il metodo dir() sovrascrivendo il metodo __dir__() nella classe. Come esempio, possiamo rimuovere i metodi interni dal risultato restituito dal metodo dir():

__eq__

Questo metodo viene chiamato quando tentiamo di eseguire l’operazione ==. Consideriamo che due oggetti umani sono uguali quando il loro attributo id è uguale anche se il loro nome è diverso.

Possiamo sovrascrivere il metodo __eq__() per ottenere questa funzionalità:

def __eq__(self, other):
return self.id == other.id

Questo ora restituirà True:

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
print(first == second)

__format__

Quando tentiamo di eseguire string.format(), il metodo __format__() viene invocato internamente.

__ge__

Come esempio, supponiamo che ci siano due oggetti Human:

first = Human(1, 'Farhad')
second = Human(2, 'Malik')

Consideriamo anche che il nostro progetto abbia una regola che un oggetto Human con un Id maggiore sia considerato maggiore dell’altro oggetto Human. Pertanto, possiamo sovrascrivere il metodo __gt__() e implementare la logica personalizzata:

def __ge__(self, other):
return self.id >= other.id

Questo ora restituirà False perché l’id del secondo oggetto umano è maggiore del primo oggetto umano:

print(first >= second)
Returns False

C’è anche un metodo __lt__() che viene eseguito quando viene eseguito l’operatore ≤.

__hash__

Hashing è usato per convertire un oggetto in un intero. L’hashing viene eseguito quando tentiamo di impostare un elemento in un dizionario/set.

Un buon algoritmo di hashing risulta in un minor numero di collisioni di hashing. Possiamo fornire il nostro algoritmo di hash sovrascrivendo il metodo __hash__().

Consideriamo che l’id dell’oggetto Human deve essere unico nella nostra applicazione. L’algoritmo __hash__() può essere sovrascritto per restituire il self.id come intero hash:

def __hash__(self):
return self.id

Possiamo creare due oggetti e salvarli in una collezione di set. Quando interroghiamo la lunghezza dell’insieme, ci aspettiamo due elementi nell’insieme perché entrambi gli oggetti hanno un id diverso.

first = Human(1, 'Farhad')
second = Human(2, 'Malik')
my_set = set()
print(len(my_set))
#returns 2

Se ora impostiamo l’id a 1 per entrambi gli oggetti e ripetiamo l’esercizio, allora vedremo solo 1 elemento nell’insieme perché entrambi gli oggetti hanno la stessa chiave hash poiché il loro attributo id è lo stesso, anche se il loro attributo name è diverso.

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
my_set = set()
print(len(my_set))
#returns 1

__init__

Il metodo __init__() viene eseguito quando vogliamo istanziare una nuova istanza di una classe chiamando il suo costruttore.

Come istanza, quando abbiamo tentato di eseguire:

human = Human(1, 'farhad')

Allora è stato eseguito il metodo __init__().

Possiamo sovrascrivere la funzionalità e passarvi i nostri argomenti e comportamenti personalizzati.

Come esempio, il metodo __init__() della classe Human è:

def __init__(self, id, name, addresses=, maps={}):
self.id = id
self.name = name
self.addresses = addresses
self.maps = maps

Foto di Cederic X su Unsplash

__init_subclass__

Questo è uno degli usi della metaclasse. Quando una classe viene sottoclassata e il suo oggetto viene creato, viene chiamato il metodo __init_subclass__().

In sostanza, il metodo informa il genitore che è stato sottoclassato. Questo gancio può quindi inizializzare tutte le sottoclassi di una data classe. Pertanto, il metodo viene utilizzato per registrare le sottoclassi e assegnare valori predefiniti agli attributi delle sottoclassi. Quindi, ci permette di personalizzare l’inizializzazione delle sottoclassi.

Vi spiegherò come funzionano le metaclassi nel mio prossimo articolo.

__new__

Quando vogliamo istanziare/creare una nuova istanza di una classe, viene eseguito il metodo __new__(cls).

Come esempio, consideriamo che vogliamo stampare ‘Human creating…’ ogni volta che viene chiamato il costruttore Human().

Possiamo sovrascrivere la funzionalità del metodo __new__(cls) come mostrato di seguito:

def __new__(cls, *args, **kwargs):
print('Human creating...')
return object.__new__(cls)

Come risultato, Human creating… viene stampato quando abbiamo tentato di creare un’istanza:

human = Human(1, 'Farhad', , {'London':2, 'UK':3})

__sizeof__

Questo metodo viene chiamato quando eseguiamo sys.getsizeof(). Restituisce la dimensione dell’oggetto in memoria, in byte.

__str__

Ha stampato: id=1. name=Farhad.

La funzione __str__() dovrebbe cercare di restituire una rappresentazione user-friendly dell’oggetto.

__weakref__

Questo oggetto __weakref__ restituisce la lista dei riferimenti deboli all’oggetto target. Essenzialmente, aiuta la garbage collection a informare i riferimenti deboli che il referente è stato raccolto. Pertanto, impedisce agli oggetti di accedere ai puntatori sottostanti.

Foto di Yousef Espanioly su Unsplash

Integer

Questo ci porta alla prossima sezione dell’articolo. Possiamo vedere che la proprietà id dell’oggetto human è di tipo int. Se poi eseguiamo dir() sulla proprietà id e filtriamo i metodi che sono circondati da doppi underscore, incontreremo che ci sono in totale 62 metodi magici.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.id)))))

Spiego qui i metodi chiave:

__add__

Questo metodo viene chiamato quando tentiamo di aggiungere due numeri.

Come esempio:

human.id + 2 è lo stesso di human.id.__add__(2)

__and__

Questo metodo viene eseguito quando tentiamo di usare l’operatore & ad es.g.:

return self & another_value

__bool__

Questo metodo viene eseguito quando tentiamo di eseguire il controllo booleano su un oggetto e.

self != 123

__floordiv__

Questo metodo viene eseguito quando eseguiamo l’operatore //.

Foto di Johannes Plenio su Unsplash

__getnewargs__

Occasione per decapitare oggetti in Python. Il decapaggio crea una rappresentazione byte-stream di un oggetto in memoria che può essere salvata su disco se necessario.

Il metodo __getnewargs__() informa il processo di decapaggio su come ha bisogno di caricare nuovamente l’oggetto decapato per ricreare l’oggetto di destinazione. In particolare, come l’oggetto deve essere creato passando gli argomenti al metodo new(). Da qui, il nome ‘get new args’.

__index__

Di conseguenza, un oggetto può essere convertito in un intero eseguendo il metodo __index__(). Possiamo anche sovrascrivere il metodo e fornire la nostra funzionalità di come l’indice deve essere generato.

__invert__

Questo metodo viene eseguito quando usiamo l’operatore ~.

Come istanza:

first = Human(1, 'Farhad')
print(~first.id)

E’ lo stesso che eseguire:

first = Human(1, 'Farhad')
print(first.id.__invert__())

__lshift__

Questo metodo ci dà lo spostamento a sinistra di un valore, per esempio il valore self <<. Possiamo sovraccaricare l’operatore << sovrascrivendo il metodo __lshift__().

Nota: __rshift__() viene eseguito quando eseguiamo l’operatore >>.

__mod__

Questo metodo viene chiamato quando usiamo l’operatore %.

__neg__

Questo metodo viene eseguito quando usiamo l’operatore negativo – ad esempio.

first.id — second.id

__subclasshook__

Questo metodo può essere sovrascritto per personalizzare il metodo issubclass(). Essenzialmente, restituisce True se una classe è una sottoclasse e False se non lo è. Il metodo restituisce anche NotImplemented che permette di utilizzare l’algoritmo esistente.

Il metodo può personalizzare il risultato del metodo issubclass().

Foto di Patrick Selin su Unsplash

String

Questo ci porta alla prossima sezione dell’articolo.

Possiamo vedere che la proprietà name dell’oggetto human è di tipo string. Se poi eseguiamo dir(human.name) sulla proprietà e filtriamo i metodi che sono circondati da doppi underscore, noteremo che ci sono in totale 33 metodi magici.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.name)))))

Spiegherò qui i 4 metodi chiave poiché il resto dell’articolo ha già evidenziato la maggior parte dei metodi magici.

__contains__

Questo metodo viene eseguito quando cerchiamo di controllare se un dato carattere esiste.

__len__

Questo metodo restituisce la lunghezza della stringa. Viene eseguito quando eseguiamo il metodo len().

Se vogliamo contare solo caratteri specifici per calcolare la lunghezza della stringa allora il metodo __()__ può essere sovrascritto per fornire questa funzionalità.

__repr__

Questo metodo viene eseguito quando vogliamo creare una rappresentazione di un oggetto adatta allo sviluppatore.

Nota, il __repr__ viene eseguito quando stampiamo(oggetto) se non abbiamo un’implementazione __str__() nella nostra classe.

def __repr__(self):
return f'id={self.id} ({type(self.id)}). name={self.name} ({type(self.name)})'print(Human(1, 'Farhad'))

Questo stamperà:

id=1 (<classe ‘int’>). name=Farhad (<classe ‘str’>)

Il metodo __repr__() dovrebbe essere inteso per produrre la rappresentazione ufficiale di un oggetto.

__iadd__

Occasione, usiamo un operatore di addizione insieme all’assegnazione e.g.

self.id += 1

Questo è equivalente a self.id = self.id + 1

Il metodo iadd() viene eseguito quando eseguiamo l’addizione con l’assegnazione.

Inoltre, il metodo __ipow__() viene eseguito quando viene eseguito **=.

Il metodo magico __iand__() serve per eseguire un AND bitwise con assegnazione e __ior__() viene chiamato quando tentiamo di fare != come: i != j

List

Questo ci porta alla prossima sezione dell’articolo. La proprietà indirizzi dell’oggetto human è di tipo lista. Se poi eseguiamo dir(human.addresses) sulla proprietà addresses e filtriamo i metodi che sono circondati da doppi underscore, incontreremo che ci sono in totale 35 metodi magici.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.addresses)))))

Spiego qui i metodi chiave:

__reduce__

Quando un oggetto viene decapato, il metodo __reduce__() viene eseguito per restituire un oggetto che aiuta il decapatore a capire come costruirlo nuovamente.

__reduce_ex__

Il metodo __reduce_ex__() è preferito dal decapaggio rispetto al metodo __reduce__().

Il metodo __reduce_ex__() prende un argomento intero che è la versione del protocollo. Fornisce compatibilità all’indietro per il decapaggio ed è usato per costruire il byte-stream decapato all’oggetto.

__reversed__

Il metodo __reversed__() viene eseguito quando tentiamo di invertire una collezione nella sequenza inversa.

Viene eseguito quando viene chiamato il metodo reversed(collection) o collection.reverse(). A volte, decidiamo di alterare la funzionalità del metodo reversed().

Sovrascrivendo il metodo __reversed__(), possiamo ottenere il risultato desiderato.

Dizionario

Questo ci porta alla quinta sezione dell’articolo.

Il dizionario è uno dei principali tipi costruiti in Python. Se eseguiamo dir(human.maps) e filtriamo i metodi che sono circondati da doppi underscore, incontreremo che ci sono in totale 29 metodi magici.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.maps)))))

Tra i 29 metodi magici, spiegherò qui i 4 metodi chiave:

__delitem__

Questo metodo viene eseguito quando cancelliamo un elemento e.g.

del dictionary

__getitem__

Questo metodo viene eseguito quando cerchiamo di ottenere un elemento per una chiave:

item = dictionary

__setitem__

Questo metodo viene eseguito quando tentiamo di impostare un elemento nel dizionario:

dictionary = item

__iter__

Questo metodo restituisce un iteratore per la collezione. Un iteratore ci aiuta a iterare su una collezione.

Possiamo sovrascrivere come l’iteratore() viene eseguito sovrascrivendo il metodo __iter__().

Infine, una nota su __call__()

E se volessimo rendere il nostro oggetto richiamabile? Consideriamo di voler rendere l’oggetto human in una funzione human() richiamabile?

Il metodo __call__() ci permette di trattare le classi come funzioni.

human = Human(1, 'Farhad')
human()

Possiamo ottenere questa funzionalità fornendo l’implementazione del metodo magico __call__() nella nostra classe Human.

Questo stamperà:

Hai tentato di chiamare
Arguments: ()
Parola chiave Argomenti: {}
id=1 (<classe ‘int’>). name=Farhad (<classe ‘str’>)
Chiamata completata

Sovrascrivendo il metodo __call__(), possiamo ora implementare un decoratore per restituire un oggetto come funzione o anche chiamare quelle librerie che accettano funzioni come argomenti passando gli oggetti effettivi.

Foto di Dollar Gill su Unsplash

Sommario

Questo è un argomento di livello avanzato per sviluppatori Python e lo consiglio a tutti coloro che sono o intendono utilizzare il linguaggio di programmazione Python.

Questo articolo intendeva spiegare cosa sono i metodi magici e come possono essere usati per costruire applicazioni Python. Ha fornito una panoramica dei metodi magici più usati in una classe personalizzata, interi, stringhe, liste e tipi di dati del dizionario.

Anche se ogni metodo in Python è pubblico, la convenzione di codifica è di circondare tutti i metodi privati da doppi underscore __<metodo>__()

Questo implica che i metodi magici sono intesi come metodi privati. Significa anche che il chiamante di un oggetto non dovrebbe invocare direttamente il metodo e il metodo è invocato internamente dalla classe che ha il metodo magico. I metodi magici ci permettono di avere più controllo su come la nostra applicazione si comporta.

I metodi magici possono essere sovrascritti per arricchire la funzionalità e creare una logica personalizzata che si adatta meglio alle esigenze del business.

Lo scopo di delineare i metodi magici chiave è per noi di capire se vogliamo sovrascrivere questi metodi nelle nostre classi personalizzate per arricchire le applicazioni.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.