Autour du codeDevelopper toujours mieux
Posté le

Les bases de la POO en python

En programmation orienté objet (POO) on manipule des objets mais qu'est-ce qu'un objet ?

Un objet peu représenter quelque chose de la vie réelle, exemple La touche 'A' de votre clavier. On distingue deux choses:

  • Comment est l'objet (son état)
  • Ce que l'on peut faire avec l'objet

Comment est la touche: avec un 'A', enfoncée ou relâchée. Ce que l'on peut faire avec la touche: L'enfoncer ou la relâcher. Les actions que l'on fait sur un objet s'appelle des méthodes, ce sont des fonctions qui permettent de modifier l'état de l'objet.

Les objets sont fabriqués par des classes que l'on a définies dans notre programme. La fabrication d'un objet s'appelle l'instanciation. En python, on utilise le mot clef class pour définir une classe.

class Touche:
    pass

Et on fabrique l'objet comme ceci.

ma_touche = Touche()
print ma_touche
<__main__.Touche object at 0xb76f202c>

Bon c'est super, mais pour le moment on a une classe qui fabrique un objet sans état avec lequel on ne peut rien faire. Pour remédier à cette frustration on va ajouter de quoi initialiser l'état de notre objet: la méthode __init__.

class Touche:
    def __init__(self, lettre, est_enfonce):
        self.lettre = lettre
        self.est_enfonce = est_enfonce

En python, la méthode pour initialiser notre objet s'appelle __init__. Comme toutes les méthodes, elle prend comme premier paramètre self qui n'est rien d'autre que notre objet puis elle ajoute à cet objet les attributs lettre et est_enfonce.

>>> ma_touche_p = Touche('p', True)
>>> ma_touche_y = Touche('y', True)
>>> print ma_touche_p.lettre
'p'
>>> print ma_touche_p.est_enfonce
True
>>> print ma_touche_y.lettre
'y'

Comme vous l'avez remarqué, on accède aux attributs d'un objet en utilisant un point (mon_objet.mon_attribut), on peut aussi les modifier de cette façon.

>>> ma_touche_y.est_enfonce = False
>>> print ma_touche_y.est_enfonce
False

Mais voilà, faire comme ceci peut poser un problème.

ma_touche_y.lettre = "Z"
ma_touche_y.est_enfonce = "J'sais pas mon chat est sur le clavier"

Effectivement on ne s'attend pas à ce que la touche 'Y' devienne 'Z' et quand on demande si la touche est enfoncée on s'attend à vrai ou faux. Pour remédier à cela, on va mettre un _ devant les noms de nos attributs, c'est une convention en Python qui signifie je ne veux pas que l'on touche directement a ces attributs. Puis on va ajouter des méthodes pour modifier l'attribut est_enfoncé de manière correcte.

class Touche:
    def __init__(self, lettre, est_enfonce):
        self._lettre = lettre
        self._est_enfonce = est_enfonce

    def enfonce(self):
        self._est_enfonce = True

    def relache(self):
         self._est_enfonce = False

    def afficher(self):
        if self._est_enfonce:
            print("La touche {} est enfoncé".format(self._lettre))
        else:
            print("La touche {} est relaché".format(self._lettre))

>>> touche_t = Touche('t', True)
>>> touche_t.enfonce()
>>> touche_t.relache()
>>> touche.afficher()
La touche t est enfoncé

J'imagine que certain d'entre vous ont déjà flairé l'arnaque et pense à faire ceci. wink

ma_touche = Touche("hello", 42)

Pour remédier à cette porte ouverte à toutes les fenêtres on va vérifier que les données passées à __init__ soie du bon type. En python, tout est objet et connaître le type d'un objet revient à savoir quelle classe à fabriquer mon objet. La fonction pour savoir si un objet est une instance d'une classe est isinstance.

>>> isinstance(ma_touche_t, Touche)
True
>>> isinstance(42, Touche)
False
>>> isinstance(42, int)
True

Grace à isinstance on va pouvoir tester les paramètres d'entrée de notre methode __init__.

Et s'ils ne sont pas bon on fait quoi ? undecided

On va générer une exception. Une exception si vous n'en avez jamais vu c'est ça

int("Chuck Norris")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>;
ValueError: invalid literal for int() with base 10: 'Chuck Norris'

La fonction int viens de lancer une exception ValueError pour dire que non, elle ne fera pas de "Chuck Norris" un nombre entier. Pour lancer une exception on utilise le mot clef raise suivi d'un objet exception.

mon_exception = ValueError("Je veux un multiple de 10")
raise mon_exception
# On n'est pas obligé de passer par une variable
raise ValueError("Je veux un multiple de 10")

Ok donc si on applique ça à notre fonction __init__, ça donne ça.

class Touche(object):
    def __init__(self, lettre, est_enfonce):
        if not isinstance(lettre, str):
            raise TypeError("First argument must be a sting")
            if len(lettre) != 1:
                raise ValueError("First argument must have len equal to 1")
        if not isinstance(est_enfonce, bool):
            raise TypeError("Second argument must be a boolean")
        self._lettre = lettre
        self._est_enfonce = est_enfonce

    def enfonce(self):
        self._est_enfonce = True

    def relache(self):
        self._est_enfonce = False

    def afficher(self):
        if self._est_enfonce:
            print "La touche %s est enfoncé"
        else:
            print "La touche %s est relaché"

J'ai utilisé deux types d'erreur différent, TypeError quant ce n'est pas le bon type qui est passé en paramètre et ValueError quant c'est le bon type mais pas la bonne valeur.

Donc en résumé,

  • Les objets sont instanciés (fabriqué) par des classes
  • L'état d'un objet est représenté par un ensemble d'attributs.
  • Pour que l'état d'un objet reste cohérent, on utilise des méthodes pour manipuler ses attributs.
  • isinstance permet de savoir de quel type est un objet.

Ce que l'on a vu sur le fait de garder l'état d'un objet cohérent en utilisant des méthodes s'appelle l'encapsulation, c'est la base de la base de la POO. Il nous reste deux choses à voir qui sont l'Héritage et la redéfinition de méthode.

J'espère que ça vous a plu, à vos IDE et bon code smile