Dunders¶
Par Marc Friedli 1
Introduction¶
Le mot dunder est un raccourci de Double UNDERscore et représente toutes les méthodes Python qui commencent et finissent par un double underscore (par ex. __init__()
).
Les dunders sont des méthodes très puissantes et régulièrement utilisées en python. Ce sont des méthodes universelles que toute classe possède (un peu à l’image de la class Object dans Java).
Cependant, dans python, les dunders sont rarement appelés directement.
Par exemple :
toto = new MyClass()
fera appel aux méthodes __new__()
et __init__()
même si ces méthodes n’ont pas été surchargées.
Les dunders ont des méthodes raccourci qui vont directement les appeler (autre raison pour laquelle on utilise régulièrement les dunders sans s’en rendre compte) :
>>> 4 + 5
9
fera appel au dunder __add__()
Ou bien :
str("I'm a text")
fera appel au dunder __str__()
de str
.
La grande puissance des dunders est leur universalité. En effet, Python a été programmé de manière à ce qu’une opération soit toujours relié au même dunder.
Par exemple, dans beaucoup de langages, si on veut connaitre la taille d’un truc, il faut d’abord savoir de quoi on parle (un tableau, une liste, un string etc.) avant de faire appel à la méthode appropriée. Dans python, on utilise toujours le dunder __len__() :
"""Travis est très très compliqué."""
class SomeClass:
"""Class d'exemple."""
grammar = {
1: "Some string",
2: "Another string",
3: 42,
4: True
}
def __init__(self, name):
"""Initialisateur."""
self.name = name
sc = SomeClass("Totoro")
print(len(sc.name)) # affiche la taille du string (6)
print(len(sc.grammar)) # affiche le nombre d'éléments (4)
Voir: Python and the Principle of Least Astonishment
Une autre grande utilisation des dunders consiste à les surcharger de manière à les personalisé.
De plus, par convention, on déclare une méthode privée comme étant un dunder :
def __myPrivateMethod__(self, other):
return none;
Exemples¶
"""Travis est trop compliqué."""
class Main:
"""Class simulant une main aux cartes."""
def __init__(self, *args):
"""Initialisateur."""
self.cartes = args
def ajouter(self, carte):
"""Méthode pour add une carte."""
obj2 = list(self.cartes) # Converti en liste
obj2.append(carte)
self.cartes = tuple(obj2)
def __str__(self):
"""Redéfinition de str."""
return str(u'; '.join(self.cartes).encode('utf8'))
def __len__(self):
"""Redéfinition de len."""
return len(self.cartes)
def __getitem__(self, key):
"""Est appelé quand on fait objet[index] ou objet[key]."""
return self.cartes[key]
def __iter__(self):
"""Est appelé quand on fait un iter(objet)."""
return iter(self.cartes)
def __reversed__(self):
"""Est appelé quand on fait reversed(objt)."""
return reversed(self.cartes)
def __contains__(self, item):
"""Est appelé quand "in objet"."""
return item in self.cartes
main = Main('1Coeur', '7Pique')
print(str(main))
main.ajouter('AsCoeur')
# parc qu'on a défini __iter__!
for carte in main:
print(carte)
# 1Coeur
print(main[0])
# False
print('3Coeur' in main)
# 3
print(len(main))
Différence entre str
et repr()
¶
Les deux servent à afficher l’objets mais pas de la même manière :
- str
se veut d’être lisible, il donne les informations qu’un utilisateur veut savoir et non le programme. Elle est ambigu et permet donc facilement le cast.
- repr
se veut au contraire non ambigu. Il doit représenter l’objet tel qu’il est réellement.
De base, quand on chercher à afficher un objet, à moins que la méthode __str__()
soit redéfinie, c’est la méthode repr qui sera appelée.
print(str(3)==str("3")) # return True car str est ambigu
print(repr(3)==repr("3")) # return False car non-ambigu (Int != String)
Idéalement, il faudrait toujours redéfinir la méthode __repr__()
et redéfinir __str__()
uniquement si on a besoin de l’ambiguïté.
Conclusion¶
Il existe beaucoup de dunders. Il faut puiser dans la doc afin de connaitre ceux dont on a l’usage et savoir quand ils sont utilisés. Ce sont de puissants outils de Python qui permettent de facilement spécialiser le comportement d’un objet.
Bibliographie¶
The Python Data model, extrait de Fluent Python