Py: OOP 6 Méthodes magiques et surcharge Op
Py: OOP 6 Méthodes magiques et surcharge Op

Programmation orienté objet

Directory

serie

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