Programmation orienté objet
Directory
serie
- partie 1: Introduction
- partie 2: En pratique
- partie 3: Attributs
- partie 4: Héritage
- partie 5: Héritage multiple et composition
- partie 6: Méthodes magiques $ \Leftarrow $
sources
Méthodes magiques
Les méthodes automatiques (ou magiques) sont les méthodes nommées avec deux doubles underscore (def __methode__(self)
).
del
C’est la sémantique inverse d’__init__
, c’est une méthode appelée quand l’objet est détruit.
import time
class Action(object):
def __del__(self):
print("Destruction")
>>> a = Action()
>>> del a
Destruction
>>> time.sleep(1) # laisse le temps au GC de faire son travail
On l’utilise pour nettoyer une fois qu’un objet n’est plus utile (fermer les sockets, les fichiers,…).
ATTENTION
Le mot clé del
en Python ne détruit pas un objet. Il détruit la référence. C’est l’interpreteur Python qui compte les références des objets, et quand un objet n’a plus de référence pointant vers lui, il est marqué pour suppression.
Ensuite, le garbage collector (GB) arrive. Ceci n’est pas prédictible . Il peut arriver tout de suite après, ou mille opérations plus tard. Et une fois qu’il est là, il supprime tous les objets marqués pour suppression.
Alors seulement __del__
est appelée.
En gros, __del__
peut être appelée beaucoup plus tard que ce qu’on ne pense, voir même pas dutout (si le scripte s’arrête avant). D’où le `time.sleep(1) dans le code pour donner de grandes chances à la méthode d’être appelée.
str et repr
Représentation interne d’un objet pour la machine:
class Chanson(object):
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
>>> morceau = Chanson("Taro", "Alt-j")
>>> print(morceau)
<__main__.Chanson object at 0x7feb534296a0>
La classe de laquelle l’objet est issu et son adresse en mémoire.
Quand nous faisons print()
, la méthode __str__
récupère la valeur à afficher. C’est le même comportement que d’appeler str()
sur un objet:
>>> dico = {'a':1, 'b':2}
>>> print(dico)
{'b': 2, 'a': 1}
>>> str(dico)
"{'b': 2, 'a': 1}"
On peut donc coder la méthode __str__
pour obtenir ce résultat:
class Chanson(object):
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
def __str__(self):
return "{} de {}".format(self.titre, self.auteur)
>>> morceau = Chanson("Taro", "Alt-j")
>>> print(morceau)
Taro de Alt-j
__repr__
est ce que Python va utiliser quand on entre un objet dans un terminal sans faire print()
:
class Chanson(object):
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
def __repr__(self):
return ("Chanson({}, {})"
.format(repr(self.titre), repr(self.auteur)))
>>> morceau = Chanson("Taro", "Alt-j")
>>> print(repr(morceau))
Chanson('Taro', 'Alt-j')
>>> print(Chanson("Animus Vox", "Glitch Mob"))
Chanson('Animus Vox', 'Glitch Mob')
Surcharge des opérateurs
En Python on ne peut pas surcharger les opérateurs comme en C++. Mais comme les opérateurs ne font qu’appeler des méthodes nagiques, on peut simplement overrider ces méthodes magiques.
Base
class Test(object):
def __init__(self, nom):
self.nom = nom
def __str__(self):
return self.nom
Mathématique
# Override opérateur +
def __add__(self, other):
return Test(str(self) + ' et ' + str(other))
# Override opérateur -
def __sub__(self, other):
return Test(str(self) + ' sans ' + str(other))
# Override opérateur *
def __mul__(self, other):
return Test(str(self) + ' fois ' + str(other))
# Override opérateur /
def __div__(self, other):
return Test(str(self) + ' divise par ' + str(other))
# Override opérateur %
def __mod__(self, other):
return Test(str(self) + ' % ' + str(other))
# Override opérateur **
def __pow__(self, other):
return Test(str(self) + ' ** ' + str(other))
Logique bit à bit
# Override opérateur <<
def __lshift__(self, other):
return Test(str(self) + ' lshift ' + str(other))
# Override opérateur >>
def __rshift__(self, other):
return Test(str(self) + ' lshift ' + str(other))
# Override opérateur &
def __and__(self, other):
return Test(str(self) + ' & ' + str(other))
# Override opérateur |
def __or__(self, other):
return Test(str(self) + ' | ' + str(other))
Logique
# Override opérateur <
def __lt__(self, other):
return Test(str(self) + ' <= ' + str(other))
# Override opérateur <=
def __le__(self, other):
return Test(str(self) + ' <= ' + str(other))
# Override opérateur ==
def __eq__(self, other):
return Test(str(self) + ' == ' + str(other))
# Override opérateur !=
def __ne__(self, other):
return Test(str(self) + ' != ' + str(other))
# Override opérateur >
def __gt__(self, other):
return Test(str(self) + ' > ' + str(other))
# Override opérateur >=
def __ge__(self, other):
return Test(str(self) + ' >= ' + str(other))
Autres
class Array(object):
def __init__(self, lenght):
self._lst = [0] * (lenght)
def __setitem__(self, key, other):
self._lst[key] = other
def __getitem__(self, key):
return self._lst[key]
Cast
class Degre(object):
def __init__(self, valeur, degre='C'):
self.valeur = valeur
self.degre = degre
def __str__(self):
return "{}°{}".format(self.valeur, self.degre)
# Comportement quand on converti en int
def __int__(self):
return int(self.valeur)
# Comportement quand on converti en float
def __float__(self):
return float(self.valeur)
# Comportement quand on converti en str
def __str__(self):
return str(self.valeur)
# etc...
Programmation dynamique
class Test(object):
# Est appelée quand on demande un attribut
# appelé "name" et qui n'existe pas.
def __getattr__(self, name):
return None
# Est appelée quand on assigne une valeur "value" à
# un attribut appelé "name", qu'il existe ou non.
# L'inverse se fait avec __delattr__ qui réagit à
# del obj.attr
def __setattr__(self, name, value):
print("Merci")
super(Test, self).__setattr__(name, value)
>>> ob = Test()
>>> print(ob.poule) # pas d'erreur !!
None
>>> print(ob.cochon)
None
>>> ob.vache = "meuh"
Merci
>>> print(ob.vache)
meuh
Conteneurs
class Main(object):
def __init__(self, *args):
self.cartes = args
def ajouter(self, carte):
assert hasattr(carte, upper), "Carte doit être une str"
self.carte.append(carte.upper())
def __str__(self):
return str(''.join(self.cartes).encode('utf8'))
# len(objet)
def __len__(self):
return len(self.cartes)
# operateur []
def __getitem__(self, key):
return self.cartes[key]
# objet[key] = value
def __setitem__(self, key, value):
self.cartes[key] = value
# del objet[index]
def __delitem__(self, key):
raise TypeError("lalala")
# Appelé quand on fait un iter(objet), en
# particulier cela arrive à chaque boucle for
# La valeur retournée doit être un iterateur.
# En général on retourne une valeur retournée
# par iter()
def __iter__(self):
return iter(self.cartes)
# Appelé quand on fait reversed(objet)
def __reversed__(self):
return reversed(self.cartes)
# Appelé quand "in objet"
def __contains__(self, item):
return item in self.cartes
>>> main = Main(u'1Coeur', u'7Pique')
>>> print(main)
1Coeur
>>> for carte in main: # parce qu'on a défini __iter__
... print(carte)
>>>
b'1Coeur7Pique'
7Pique
>>>
>>> print(main[0]) # __getitem__
1Coeur
>>> print('fdjsklfd' in main) # __contains__
False
>>> print(len(main))
2