La Programmation Orientée Objet

Temps de lecture : 11 minutes
Share on facebook
Share on twitter
Share on linkedin
Share on email

Programmation Orientée Objet

Dans ce dossier consacré à la programmation orientée objet, vous allez découvrir progressivement tous les concepts indispensables à sa compréhension.
Nous vous souhaitons un bon voyage au pays des classes, des instances, des constructeurs, de l’héritage, de l'encapsulations, des méthodes et autres concepts de la programmation orientée objet.

Le langage de programmation Python est un langage très accessible pour commencer la programmation. Grâce à ses librairies numpy, pandas, sklearn et matplotlib, il est aussi un outil formidable pour les Data Scientists. 

La facilité avec laquelle Python peut être abordée vient notamment de ses multiples paradigmes: Python peut être utilisé de manière impérative, en suivant un paradigme fonctionnel ou en implémentant les règles de la Programmation Objet.

Dans ce dossier, nous allons définir la programmation orientée objet, ses grands principes et particularismes et son utilisation en Python. Nous verrons en quoi cette notion est très importante pour les Data Scientists comme pour les Data Engineers sans être pour autant trop difficile d’accès.

Dans cette série, nous parlerons de classes, d’instances, de méthodes, d’attributs. Si ces termes peuvent sembler obscurs à des Data Scientists junior, ces concepts sont pourtant manipulé tout le temps en Python, en particulier avec les librairies mentionnées plus tôt.

Ce paradigme est aussi très présent dans d’autres langages de programmation, que ce soit de manière totale (Java ou C#) ou partielle (C++, Ruby, Scala, …).

Si on se réfère au sondage de Stack Overflow concernant les technologies utilisées, Java et C# sont utilisés par 41 et 31% des développeurs interrogés

Python peut donc servir de porte d’entrée à ces langages très utilisés. 

Il est donc essentiel de maîtriser ce paradigme car on le retrouvera dans des outils comme Spark ou Hadoop.

programmation orientee objet
Apprendre la programmation orientée objet permettra de s’adapter à beaucoup d’outils

Commencer à maîtriser la programmation orientée objet avec Python est donc une atout précieux pour ensuite s’intéresser à des outils très utilisés, en Data Science comme en Big Data.

Enfin, la programmation orientée objet est un outil incroyable pour améliorer la qualité, la lisibilité et la modularité de votre code. Le concept d’héritage permet notamment de simplifier la personnalisation de contenu créé par d’autres. 

Par exemple, dans le cadre de nos formations, nous avons pu créer des outils qui se comportaient comme des clients Python de base de données mais qui en fait lançaient et communiquaient avec des images Docker de ces bases de données. 

La personnalisation des modèles de Machine Learning est également très facile à mettre en oeuvre grâce à la programmation orientée objet

Si vous avez lu l’introduction , vous n’êtes plus à convaincre quant à l’utilité de la programmation orientée objet . Nous vous avions promis un voyage de découverte des classes, constructeurs et autres concepts indispensables à sa compréhension puis à son utilisation sur Python. Démarrons avec l’un des concepts fondamentaux: les classes .

Le concept de classe en programmation orientée objet

La notion la plus importante en programmation orientée objet est le concept de classe. Les classes sont des moules, des patrons qui permettent de créer des objets en série sur le même modèle. On peut se représenter une classe comme le schéma de construction ainsi que la liste des fonctionnalités d’un ensemble d’objets.

programmation orientée objet
Définir une classe permet de créer des objets identiques, à la chaîne

En programmation orientée objet, on n’a affaire qu’à des classes et des objets (ou instance de classe). Tous les éléments manipulés en programmation objet sont des objets (d’où le nom) dont la construction repose sur la définition d’une classe

Comment créer une classe avec Python ?

En utilisant ce modèle, nous pourrons alors créer des objets, appelées instances. Pour créer des classes en Python, on utilise le mot-clef class:

 Dans cet exemple, on crée une classe nommée Animal. On choisit ainsi le modèle qu’auront les différentes instances de la classe Animal. Pour l’instant cette classe est très simple mais nous la complexifierons plus tard.  

Pour instancier cette classe, nous allons appeler la classe Animal comme si elle était une fonction:

Ici, nous avons créé deux objets, deux instances de la classe Animal: animal1 et animal2. Si on affiche le type de ces deux objets grâce à la fonction type, on voit que ces deux objets appartiennent bien à la classe Animal.

python classe
Ne pas confondre: définir le python comme un animal classe et définir une classe animal en Python

Les constructeurs

Les constructeurs sont des fonctions (pas exactement mais gardons ce mot pour l’instant) très importantes: ce sont les fonctions qui sont appelées lorsqu’un objet est créé. Dans le premier cas que nous avons montré, nous n’avions rien de particulier à construire donc nous n’avons pas défini le constructeur. 

Mais dans la plupart des cas, il faut le définir. Si nous voulons ajouter un affichage qui nous prévient de la création d’un objet de la classe Animal,  nous allons définir la fonction __init__ comme suit:

Cette fonction __init__ prend un argument très important: self. Ce mot-clef désigne l’objet lui-même. Pour l’instant cette utilisation est un peu confuse mais nous verrons pourquoi ce mot est si important par la suite.

Les attributs

Notre classe n’a pas un grand intérêt pour l’instant… Nous devons ajouter des caractéristiques à nos animaux pour qu’ils soient intéressant. 

Ces caractéristiques sont ce qu’on appelle les attributs. 

Par exemple, nous allons donner un âge à tous nos objets de la classe Animal:

Dans la fonction __init__, on a ajouté la définition d’une variable self.age qui vaut 0. 

En fait, lorsqu’on définit self.<quelquechose>, on définit un attribut des objets de la classe.

Ainsi toutes les instances de la classe Animal auront un attribut age qui lors de la création de l’objet vaudra 0.

Pour accéder aux attributs d’un objet, on utilise un point. Ainsi dans cette exemple, on accède à l’attribut age de animal1 et de animal2.

Il faut donc voir les attributs comme les caractéristiques des objets d’une classe: tous nos objets qui seront créés à partir de cette classe (qui instancieront cette classe), posséderont ces mêmes caractéristiques.

En les définissant dans la fonction __init__, toutes les instances de cette classe auront ces caractéristiques. 

On peut modifier ces valeurs, leur donner une toute autre valeur objet par objet sans problème mais elles seront initialisées de la même façon:

classe_poo
classe_poo
classe_poo

Différentes idées de la classe

Dans cette partie, nous avons vu que les classes sont des modèles pour des objets, que ces modèles peuvent avoir des caractéristiques représentées par des attributs et enfin que ces objets ou instances de classe, sont créés grâce à une “fonction” spéciale, le constructeur.

 

Il nous reste encore plusieurs sujets à définir pour faire de vous de vrais As de la POO … Parmi eux, les méthodes : 

Les méthodes

Dans la section précédente , nous avons défini les attributs, c’est-à-dire, les caractéristiques d’un objet d’une classe.

Petit rappel : Si la classe prise est Citoyen, les attributs des instances de cette classe peuvent être nom, prénom, sexe, date de naissance, lieu de naissance, l’identifiant, la signature et la taille.

Définir une classe Citoyen serait donc comme définir un modèle de carte d’identité vide
Nous avons vu que ces attributs sont définis dans la “fonction” __init__. Or cette affirmation est fausse: __init__ n’est pas une fonction mais une méthode.

Fonctions vs Méthodes

Si fonctions et méthodes peuvent prendre des entrées et renvoyer des sorties, les fonctions ne sont pas relatives à un objet. En fait, en programmation orientée objet pure, les fonctions n’existent pas puisque tout est objet, c’est-à-dire instance de classe.

methode
Autre article connu sur la méthode

Grâce à une méthode, on va pouvoir réaliser des opérations qui sont spécifiques à un objet: modifier ses attributs, les afficher, les retourner (ou les initialiser dans le cas de la méthode __init__).

Définition d’une méthode

Si nous revenons à notre exemple de la classe Animal pour laquelle on a défini un attribut age. La méthode __init__ permet d’initialiser l’attribut age à 0. Nous allons pouvoir créer une méthode vieillir qui va ajouter 1 à l’attribut age:

Si on s’intéresse à la définition de cette méthode plus en détail, on remarque l’utilisation du mot-clef def qui introduit normalement la définition d’une fonction. Mais cette déclaration de méthode est indentée de manière à se retrouver “à l’intérieur” de la définition de la classe.
De plus on remarque que l’argument self est encore utilisé. Nous verrons plus tard qu’il n’est pas toujours nécessaire d’utiliser cet argument.
Ainsi grâce à cette définition, tous les objets qui sont des instances de cette classe Animal auront la possibilité d’appeler cette méthode:

Pour appeler une méthode d’instance, on utilise un point et puisqu’il s’agit d’une sorte de fonction, on utilise des parenthèses. D’ailleurs la définition d’une méthode fonctionne de la même façon que celle d’une fonction pour les arguments, les sorties …

On pourra ainsi utiliser des arguments supplémentaires à self.

Par exemple, si on veut donner un nom à nos animaux, on peut introduire un attribut nom dans la méthode __init__ et construire une méthode nommer qui permettra de changer la valeur de l’attribut nom:

Passer des arguments dans une méthode est évidemment très simple:
On notera qu’on peut tout à fait ne pas définir l’attribut nom dans le constructeur et ne le définir dans la méthode nommer mais ce n’est pas recommandé. On peut aussi passer des arguments à la méthode __init__ si on veut personnaliser les initialisations.

Point d’étape

Jusqu’à présent, nous avons vu le concept de classe et d’instance de classe. Nous avons également défini les attributs et les méthodes. Il s’agit du coeur de la programmation orientée objet. On pourrait presque s’arrêter là. Ce qu’on ferait par la suite en utilisant ces outils serait de la POO simple, sans tirer avantage de tous les concepts de POO (Ce serait quand même dommage …).

Il faut garder à l’esprit que la Programmation Orientée Objet repose sur le concept de modélisation qui est essentiel en ingénierie (que ce soit ingénierie logicielle, Data Science, statistiques, …). 

On choisit d’approcher un objet réel par une modélisation, de toute façon imparfaite mais la plus fonctionnelle possible, de l’objet.

Faisons donc une pause dans les concepts pour essayer de comprendre pourquoi ce que nous faisons ici est important: lorsque vous avez commencé à faire du Machine Learning avec scikit-learn, on vous a parlé très vite de la régression logistique et de son utilisation. Mais regardons l’implémentation (simplifiée) de cette régression logistique dans la librairie scikit-learn.

On remarque que LogisticRegression est une classe. Elle possède une méthode __init__ qui peut prendre différents arguments. Elle a aussi d’autres méthodes fit, predict, predict_proba, … Elle possède les attributs random_state, verbose, n_jobs, …
Ainsi si on prend un code classique de Data Scientist, on peut parler en terme de programmation orientée objet:

La programmation orientée objet est donc essentielle pour le Data Scientist !

Nous avons vu dans cette partie que les méthodes sont des fonctions propres à des instances d’une classe et comment les définir et les appeler avec Python. Dans la suite, nous parlerons de certaines méthodes, les accesseurs et les mutateurs, ce qui nous permettra de parler d’un concept important de programmation orientée objet: l’encapsulation.

Encapsulation

Dans la partie précédente, nous avons vu comment définir une méthode. En programmation orientée objet, on peut définir des méthodes particulières appelées getters et setters, ou dans la langue de Molière, accesseurs et mutateurs.

Accesseurs et Mutateurs

Les accesseurs sont des méthodes qui permettent de retourner la valeur d’un attribut. 

Les mutateurs permettent de modifier la valeur d’un attribut.

Mais pourquoi utiliser ces méthodes alors que l’on peut facilement modifier ou lire ces attributs comme on l’a vu précédemment?

En fait, l’utilisation de ces méthodes fait partie du concept d’encapsulation: on veut contrôler les accès en écriture ou en lecture des attributs. Par exemple, je peux vouloir contrôler le type ou la valeur qu’on peut donner à un attribut. En utilisant un mutateur (setter), je cache donc la modification de la valeur à un utilisateur en lui faisant utiliser cette méthode qui pourra alors contenir mes conditions de modification.

Encapsulation des attributs

En programmation orientée objet, on distingue différents types d’attributs: les attributs publics, les attributs protégés et les attributs privés. Nous reviendrons sur les attributs protégés rapidement lorsque nous parlerons d’héritage mais la différence importante est entre les attributs privés et les attributs publics. 

Un attribut privé n’est accessible qu’à l’intérieur de la définition de la classe: je ne pourrais y accéder (pour lecture ou écriture) que dans la définition des différentes méthodes. A contrario, les attributs publics sont accessibles partout et toujours. 

Pour l’instant nous n’avons eu affaire qu’à des attributs publics. 

Dans un langage comme Java, cette différence est très stricte. Lorsqu’on utilise Python, on ne rend jamais totalement un attribut privé: Python est un langage beaucoup plus ouvert pour “adultes consentants”, c’est-à-dire qu’on fait en quelque sorte confiance à l’utilisateur final pour qu’il ne cherche pas à détruire le code. 

Mais il existe tout de même des façons de faire en sorte que les attributs soient moins accessibles. 

Attributs privés en Python

Nous allons modifier la définition de notre classe pour permettre de créer un attribut privé age (après tout, pourquoi aurait-on le droit de demander son âge à un animal qu’on ne connaît pas ?). 

Pour définir un attribut privé, on va nommer cet attribut en commençant par __ mais faîtes attention, car si vous terminez le nom de l’attribut par __ aussi, alors il n’est plus considéré comme privé: il a simplement un nom plus compliqué.

La dernière ligne devrait générer une exception AttributeError: l’attribut __age n’existe pas…Mais on remarque que la méthode vieillir a bien fonctionné: l’attribut __age existe bien à l’intérieur de la définition de la méthode. 

On a donc bien crée un attribut privé… ou alors il est simplement caché. En fait, il est disponible mais sous un autre nom: _Animal__age.

Dans d’autres langages de programmation, c’est ici que les accesseurs et les mutateurs jouent un rôle très important puisque c’est grâce à eux qu’on va pouvoir lire ou modifier ces attributs

Mais nous pouvons tout de même les définir nous-mêmes:

Dans cet exemple, les méthodes get_age et set_age servent d’accesseur et de mutateur. On voit l’intérêt d’un mutateur pour contrôler le type d’un attribut, ce qui pour un langage typé dynamiquement comme Python peut présenter un avantage.

Définir proprement accesseurs et mutateurs

Dans cette partie, nous allons utiliser les décorateurs. Si vous ne savez pas ce que sont les décorateurs, je vous invite à lire cet article avant de continuer.

Pour forcer l’accès aux attributs via les getters et les setters, on peut utiliser la classe pré-construite property:

La première définition de la méthode age permet de définir le getter et la seconde le setter. On remarque alors dans les lignes suivantes, que ce sont bien ces méthodes qui sont appelées lorsqu’on modifie ou appelle ces attributs.  

Attention: dans ce code, l’attribut age est toujours public. Ce code permet simplement de montrer comment définir proprement et simplement accesseur et mutateur.

Pour les plus courageux, vous pouvez explorer cette page pour voir que ces méthodes sont utilisés dans des librairies comme pandas ou scikit-learn.

Encapsulation des méthodes

Le même principe d’encapsulation s’applique aux méthodes: on peut définir des méthodes privées, protégées ou publiques. Les méthodes publiques sont toujours accessibles alors que les méthodes privées ne sont accessibles qu’à l’intérieur de la classe.

Le principe en Python est le même: on utilise __ au début du nom de la méthode (et pas à la fin) et la méthode est toujours retrouvable sur le modèle de _NomDeLaClasse__NomDeLaMethode.

Dans cette section , nous avons vu le principe d’encapsulation qui consiste à cacher des attributs et des méthodes de l’extérieur de la classe. On peut ainsi contrôler les accès aux attributs et aux méthodes notamment grâce aux accesseurs et aux mutateurs.

Il faut toutefois garder à l’esprit que ce sont des concepts de Programmation Orientée Objet “pure”. Python ne respecte pas vraiment ces principes puisqu’on peut toujours avoir accès à des attributs ou méthodes privés. Dans un langage vraiment orienté objet comme Java, on ne peut pas tricher. 

Dans la suite, nous aborderons le concept de méthode et d’attribut statique.

S’abonner
Notifier de
guest
0 Commentaires
Inline Feedbacks
View all comments
Fermer le menu
0
Would love your thoughts, please comment.x
()
x