Python avancé : Que sont les méthodes magiques ?

Cet article met en évidence les méthodes spéciales Python incontournables que tout programmeur Python doit connaître

Farhad Malik

Follow

16 mai, 2020 – 14 min de lecture

Les méthodes magiques nous aident à enrichir nos applications. Elles ajoutent indirectement de la magie à notre code Python. C’est un sujet de niveau avancé pour les développeurs Python et je le recommande à tous ceux qui sont/ou ont l’intention d’utiliser le langage de programmation Python.

Les méthodes magiques nous donnent plus de contrôle sur le comportement de notre application.

Cet article vise à expliquer ce que sont les méthodes magiques et comment elles peuvent être utilisées pour construire des applications Python. Il fournira un aperçu des méthodes magiques les plus largement utilisées pour une gamme de types de données.

L’objectif d’exposer les méthodes magiques clés est pour nous de comprendre si nous voulons surcharger ces méthodes dans nos classes personnalisées pour enrichir les applications.

Les méthodes magiques rendent le langage de programmation Python extrêmement puissant

Photo de Rodion Kutsaev sur Unsplash

Quelles sont les méthodes magiques Python ?

Les méthodes magiques Python sont également connues sous le nom de méthodes spéciales ou méthodes dunder. Elles sont entourées de doubles traits de soulignement, par exemple __init__().

Un objet peut avoir un certain nombre de méthodes magiques.

Souvenez-vous que tout en Python est un objet, y compris une variable/fonction/classe etc. Les objets sont l’abstraction de python pour les données.

Les méthodes magiques sont utilisées pour construire et initialiser de nouveaux objets, elles nous aident à récupérer un objet comme un dictionnaire, elles sont utilisées pour supprimer un objet entre autres opérations. Elles sont utilisées lorsque l’opérateur + est invoqué, ou même lorsque nous voulons représenter un objet sous forme de chaîne de caractères.

Bien que chaque méthode en Python soit publique, la convention de codage consiste à entourer toutes les méthodes privées par des doubles traits de soulignement __<méthode>__()

Cela implique que les méthodes magiques sont destinées à être des méthodes privées. Cela signifie également que l’appelant d’un objet ne doit pas invoquer la méthode directement car la méthode est destinée à être invoquée par la classe en interne qui a la méthode magique.

Nous pouvons surcharger les méthodes magiques pour fournir notre propre fonctionnalité personnalisée.

Photo de Cristian Escobar sur Unsplash

Je vais expliquer les concepts des méthodes magiques en créant une classe personnalisée, puis je donnerai un aperçu des méthodes magiques les plus importantes dans un entier, une chaîne de caractères, une liste et un dictionnaire.

A mesure que nous progressons dans l’article, il commencera à construire une image beaucoup plus claire de la raison pour laquelle les méthodes magiques existent et comment les utiliser.

Si vous voulez comprendre le langage de programmation Python du niveau débutant au niveau avancé, alors je vous recommande vivement l’article ci-dessous :

J’aborderai le sujet des méthodes magiques en créant une classe personnalisée, puis j’expliquerai comment les méthodes magiques sont utilisées.

La valise d’utilisation pour comprendre les méthodes magiques

Dans le snippet ci-dessous, j’ai créé une classe appelée Human, puis j’ai instancié une instance de la classe Human.

Ce bout de code sera utilisé pour nous aider à comprendre les méthodes magiques.

Notez, l’id de l’objet human est de type integer, l’attribut name est de type string, la propriété addresses est de type list et la propriété maps est de type dictionary.

J’ai regroupé les méthodes magiques dans différentes sections de type de données pour faciliter la lecture. Cependant, les mêmes méthodes magiques se retrouvent dans différents types de données.

Les méthodes magiques peuvent être surchargées pour enrichir la fonctionnalité et créer une logique personnalisée qui convient le mieux aux besoins de l’entreprise.

Class:

Comprenons les méthodes magiques qui sont associées à l’objet human. Si j’exécute la méthode dir(human), elle listera toutes les fonctions et les noms d’attributs de l’objet humain.

Il y a en tout 29 méthodes/attributs qui sont associés à l’objet humain. Parmi elles, 26 sont les méthodes magiques.

C’est un nombre assez important de méthodes spéciales. Ces méthodes sont héritées du type de base de la classe Human. Par conséquent, ce sont les méthodes prédéfinies que nous pouvons utiliser/override pour enrichir les classes.

La prochaine partie de la section expliquera les méthodes magiques clés.

__delattr__

Cette méthode est appelée lorsque nous tentons de supprimer un attribut d’une classe.

Nous pouvons surcharger la fonctionnalité en implémentant la méthode dans la classe Human:

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

Maintenant, chaque fois que nous tentons de supprimer un attribut, il affichera le message : Suppression d’un attribut

Il existe également la méthode __setattr__() pour attribuer une valeur à un attribut et __getattr__() pour obtenir une valeur de l’attribut.

Un cas d’utilisation de __delattr__() peut être d’empêcher des attributs particuliers d’un objet d’être supprimés ou lorsque nous voulons effectuer des actions spécifiques quand un attribut particulier est supprimé.

__dict__

Cette méthode retourne un dictionnaire qui représente l’objet. Les clés du dictionnaire sont les attributs de l’objet et les valeurs sont les valeurs des attributs.

Comme instance :

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

Le code ci-dessus renvoie :

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

__dir__

Nous pouvons surcharger la méthode dir() en surchargeant la méthode __dir__() de la classe. A titre d’exemple, nous pouvons supprimer les méthodes internes du résultat renvoyé par la méthode dir() :

__eq__

Cette méthode est appelée lorsque nous tentons d’effectuer l’opération ==. Considérons que deux objets humains sont égaux lorsque leur attribut id est égal même si leur nom est différent.

Nous pouvons surcharger la méthode __eq__() pour réaliser cette fonctionnalité:

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

Elle retournera maintenant True:

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

__format__

Chaque fois que nous tentons de faire string.format(), la méthode __format__() est invoquée en interne.

__ge__

En tant qu’instance, supposons qu’il existe deux objets Humains:

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

Considérons également que notre projet a une règle selon laquelle un objet humain avec un Id plus grand est considéré comme plus grand que l’autre objet humain. Par conséquent, nous pouvons surcharger la méthode __gt__() et mettre en œuvre la logique personnalisée:

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

Ceci renverra maintenant False parce que l’Id du deuxième objet humain est plus grand que le premier objet humain:

print(first >= second)
Returns False

Il existe également une méthode __lt__() qui est exécutée lorsque l’opérateur ≤ est effectué.

__hash__

Le hachage est utilisé pour convertir un objet en un nombre entier. Le hachage est effectué lorsque nous tentons de définir un élément dans un dictionnaire/ensemble.

Un bon algorithme de hachage entraîne un nombre plus faible de collisions de hachage. Nous pouvons fournir notre propre algorithme de hachage en surchargeant la méthode __hash__().

Partons du principe que l’id de l’objet Human est censé être unique dans notre application. L’algorithme __hash__() peut être surchargé pour retourner l’id de self.id en tant qu’entier de hachage:

def __hash__(self):
return self.id

Nous pouvons créer deux objets et les enregistrer dans une collection d’ensembles. Lorsque nous interrogeons la longueur de l’ensemble, nous nous attendrons à deux éléments dans l’ensemble parce que les deux objets ont un id différent.

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

Si nous fixons maintenant l’id à 1 pour les deux objets et que nous répétons l’exercice, alors nous ne verrons qu’un seul élément dans l’ensemble parce que les deux objets ont la même clé de hachage car leur attribut id est le même, même si leur attribut name est différent.

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

__init__

La méthode __init__() est exécutée lorsque nous voulons instancier une nouvelle instance d’une classe en appelant son constructeur.

En tant qu’instance, lorsque nous avons tenté d’exécuter :

human = Human(1, 'farhad')

Alors la méthode __init__() a été exécutée.

Nous pouvons surcharger la fonctionnalité et y passer nos propres arguments et comportements personnalisés également.

En tant qu’exemple, la méthode __init__() de la classe Human est :

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

Photo de Cederic X sur Unsplash

__init_subclass__

C’est l’un des cas d’utilisation des métaclasses. Quand une classe est sous-classée et que son objet est créé alors la méthode __init_subclass__() est appelée.

Essentiellement, la méthode informe le parent qu’il a été sous-classé. Ce crochet peut alors initialiser toutes les sous-classes d’une classe donnée. Par conséquent, la méthode est utilisée pour enregistrer les sous-classes et attribuer des valeurs par défaut aux attributs sur les sous-classes. Par conséquent, il nous permet de personnaliser l’initialisation des sous-classes.

J’expliquerai comment les métaclasses fonctionnent dans mon prochain article.

__new__

Lorsque nous voulons instancier/créer une nouvelle instance d’une classe alors la méthode __new__(cls) est exécutée.

En guise d’exemple, considérons que nous voulons imprimer ‘Human creating…’ chaque fois que le constructeur Human() est appelé.

Nous pouvons surcharger la fonctionnalité de la méthode __new__(cls) comme indiqué ci-dessous:

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

En conséquence, Human creating… est imprimé lorsque nous avons tenté de créer une instance:

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

__sizeof__

Cette méthode est appelée lorsque nous exécutons sys.getsizeof(). Elle renvoie la taille de l’objet en mémoire, en octets.

__str__

Il a imprimé : id=1. name=Farhad.

La fonction __str__() doit tenter de renvoyer une représentation conviviale de l’objet.

__weakref__

Cet objet __weakref__ renvoie la liste des références faibles à l’objet cible. Essentiellement, il aide le garbage collection à informer les références faibles que le référent a été collecté. Par conséquent, il empêche les objets d’accéder aux pointeurs sous-jacents.

Photo de Yousef Espanioly sur Unsplash

Integer

Cela nous amène à la section suivante de l’article. Nous pouvons voir que la propriété id de l’objet human est de type int. Si nous effectuons ensuite dir() sur la propriété id et que nous filtrons les méthodes qui sont entourées de doubles soulignés, nous rencontrerons qu’il y a au total 62 méthodes magiques.

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

Je vais expliquer ici les méthodes clés :

__add__

Cette méthode est appelée lorsque nous tentons d’additionner deux nombres.

En guise d’exemple:

human.id + 2 est identique à human.id.__add__(2)

__and__

Cette méthode est exécutée lorsque nous tentons d’utiliser l’opérateur & par exemple.g.:

return self & another_value

__bool__

Cette méthode est exécutée lorsque nous tentons d’effectuer la vérification booléenne sur un objet e.g.

self != 123

__floordiv__

Cette méthode est exécutée lorsque nous exécutons l’opérateur //.

Photo de Johannes Plenio sur Unsplash

__getnewargs__

Occasionnellement, nous décapons des objets en Python. Le pickling crée une représentation en flux d’octets d’un objet en mémoire qui peut être enregistrée sur le disque si nécessaire.

La méthode __getnewargs__() informe le processus de pickling sur la façon dont il doit recharger l’objet picklé pour recréer l’objet cible. En particulier, comment l’objet doit être créé en passant les arguments à la méthode new(). D’où le nom ‘get new args’.

__index__

Par la suite, un objet peut être converti en un nombre entier en exécutant la méthode __index__(). Nous pouvons également surcharger la méthode et fournir notre propre fonctionnalité de la façon dont l’index doit être généré.

__invert__

Cette méthode est exécutée lorsque nous utilisons l’opérateur ~.

En tant qu’instance:

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

C’est la même chose que d’exécuter:

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

__lshift__

Cette méthode nous donne le décalage à gauche d’une valeur par exemple la valeur self <<. Nous pouvons surcharger l’opérateur << en surchargeant la méthode __lshift__().

Note : __rshift__() est exécutée lorsque nous effectuons l’opérateur >>.

__mod__

Cette méthode est appelée lorsque nous utilisons l’opérateur %.

__neg__

Cette méthode est exécutée lorsque nous utilisons l’opérateur – négatif par exemple.

first.id — second.id

__subclasshook__

Cette méthode peut être surchargée pour personnaliser la méthode issubclass(). Essentiellement, elle renvoie True si une classe est une sous-classe et False si elle ne l’est pas. La méthode renvoie également NotImplemented qui permet d’utiliser l’algorithme existant.

La méthode peut personnaliser le résultat de la méthode issubclass().

Photo de Patrick Selin sur Unsplash

String

Ceci nous amène à la section suivante de l’article.

Nous pouvons voir que la propriété name de l’objet human est de type string. Si nous effectuons ensuite dir(human.name) sur la propriété et que nous filtrons les méthodes qui sont entourées de doubles soulignés, nous remarquerons qu’il y a au total 33 méthodes magiques.

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

J’expliquerai ici les 4 méthodes clés car le reste de l’article a déjà mis en évidence la plupart des méthodes magiques.

__contains__

Cette méthode est exécutée lorsque nous tentons de vérifier si un caractère donné existe.

__len__

Cette méthode renvoie la longueur de la chaîne de caractères. Elle est exécutée lorsque nous exécutons la méthode len().

Si nous voulons compter seulement des caractères spécifiques pour calculer la longueur de la chaîne, alors la méthode __()__ peut être surchargée pour fournir cette fonctionnalité.

__repr__

Cette méthode est exécutée lorsque nous voulons créer une représentation conviviale pour le développeur d’un objet.

Note, la __repr__ est exécutée lorsque nous imprimons(objet) si nous n’avons pas une implémentation de __str__() dans notre classe.

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

Ceci imprimera :

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

La méthode __repr__() devrait être destinée à produire la représentation officielle d’un objet.

__iadd__

Occasionnellement, nous utilisons un opérateur d’addition à côté de l’affectation e.g.

self.id += 1

Ceci est équivalent à self.id = self.id + 1

La méthode iadd() est exécutée lorsque nous effectuons l’addition avec l’affectation.

De plus, la méthode __ipow__() est exécutée lorsque **= est effectué.

La méthode magique __iand__() permet d’effectuer un ET bit à bit avec affectation et __ior__() est appelée lorsque nous tentons de faire != tel que : i != j

Liste

Cela nous amène à la section suivante de l’article. La propriété addresses de l’objet human est de type list. Si nous effectuons ensuite dir(human.addresses) sur la propriété addresses et que nous filtrons les méthodes qui sont entourées de doubles soulignés, nous rencontrerons qu’il y a au total 35 méthodes magiques.

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

Je vais expliquer les méthodes clés ici:

__reduce__

Lorsqu’un objet est décapé, la méthode __reduce__() est exécutée pour retourner un objet qui aide le décapé à comprendre comment le reconstruire.

__reduce_ex__

La méthode __reduce_ex__() est préférée par le pickle à la méthode __reduce__().

La méthode __reduce_ex__() prend un argument entier qui est la version du protocole. Elle fournit une compatibilité ascendante pour le décapage et est utilisée pour construire le flux d’octets décapés vers l’objet.

__reversed__

La méthode __reversed__() est exécutée lorsque nous tentons d’inverser une collection dans la séquence inverse.

Elle est exécutée lorsque le reversed(collection) ou collection.reverse() est appelé. Parfois, nous décidons de modifier la fonctionnalité de la méthode reversed().

En surchargeant la méthode __reversed__(), nous pouvons obtenir le résultat souhaité.

Dictionary

Cela nous amène à la cinquième section de l’article.

Dictionary est l’un des principaux types construits en Python. Si nous effectuons dir(human.maps) et filtrons les méthodes qui sont entourées de doubles soulignés, nous rencontrerons qu’il y a au total 29 méthodes magiques.

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

Parmi les 29 méthodes magiques, j’expliquerai ici les 4 méthodes clés:

__delitem__

Cette méthode est exécutée lorsque nous supprimons un élément e.g.

del dictionary

__getitem__

Cette méthode est exécutée lorsque nous tentons d’obtenir un élément pour une clé :

item = dictionary

__setitem__

Cette méthode est exécutée lorsque nous tentons de définir un élément dans le dictionnaire :

dictionary = item

__iter__

Cette méthode renvoie un itérateur pour la collection. Un itérateur nous aide à itérer sur une collection.

Nous pouvons surcharger la façon dont l’itérateur() est exécuté en surchargeant la méthode __iter__().

Enfin, une note sur __call__()

Que faire si nous voulions rendre notre objet appelable ? Considérons que nous voulions faire de l’objet humain une fonction human() appelable?

La méthode __call__() nous permet de traiter les classes comme des fonctions.

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

Nous pouvons réaliser cette fonctionnalité en fournissant l’implémentation de la méthode magique __call__() dans notre classe Human.

Ceci s’imprimera:

Vous avez tenté d’appeler
Arguments : ()
Mot-clé Arguments : {}
id=1 (<classe ‘int’>). name=Farhad (<classe ‘str’>)
Appel terminé

En surchargeant la méthode __call__(), nous pouvons maintenant implémenter un décorateur pour retourner un objet comme une fonction ou même appeler ces bibliothèques qui acceptent les fonctions comme arguments en passant dans les objets réels.

Photo de Dollar Gill sur Unsplash

Summary

C’est un sujet de niveau avancé pour les développeurs Python et je le recommande à tous ceux qui sont/ou ont l’intention d’utiliser le langage de programmation Python.

Cet article avait pour but d’expliquer ce que sont les méthodes magiques et comment elles peuvent être utilisées pour construire des applications Python. Il a fourni un aperçu des méthodes magiques les plus utilisées dans une classe personnalisée, des entiers, des chaînes de caractères, des listes et des types de données de dictionnaire.

Bien que chaque méthode dans Python soit publique, la convention de codage est d’entourer toutes les méthodes privées par des doubles traits de soulignement __<méthode>__()

Cela implique que les méthodes magiques sont destinées à être des méthodes privées. Cela signifie également que l’appelant d’un objet ne doit pas invoquer directement la méthode et que la méthode est invoquée par la classe en interne qui possède la méthode magique. Les méthodes magiques nous permettent d’avoir plus de contrôle sur la façon dont notre application se comporte.

Les méthodes magiques peuvent être surchargées pour enrichir la fonctionnalité et créer une logique personnalisée qui convient le mieux aux besoins de l’entreprise.

L’objectif d’exposer les méthodes magiques clés est pour nous de comprendre si nous voulons surcharger ces méthodes dans nos classes personnalisées pour enrichir les applications.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.