Accéder au contenu principal

Encapsulation dans la programmation orientée objet en Python : Un guide complet

Apprenez les principes fondamentaux de la mise en œuvre de l'encapsulation dans la programmation orientée objet de Python.
Actualisé 14 nov. 2024  · 11 min de lecture

Pourquoi apprendre l'encapsulation ?

L'encapsulation est un principe fondamental de l'orientation objet en Python. Il protège vos classes contre les modifications ou suppressions accidentelles et favorise la réutilisation et la maintenabilité du code. Considérez cette simple définition de classe :

class Smartphone:
   def __init__(self, brand, os):
       self.brand = brand
       self.os = os

iphone = Smartphone("Apple", "iOS 17")

De nombreux programmeurs Python définissent les classes de cette manière. Cependant, il est loin des meilleures pratiques que suivent les Pythonistes professionnels. Le problème de cette classe est évident lorsque vous essayez de modifier ses données :

iphone.os = "Android"
print(iphone.os)
Android

Imaginez un iPhone fonctionnant sous Android - quel scandale ! Il est clair que nous devons fixer des limites à notre classe afin que les utilisateurs ne puissent pas modifier ses attributs à leur guise.

S'ils décident de les modifier, ils doivent le faire dans le respect de nos conditions et de nos règles. Mais nous voulons que les attributs .os ou .brand restent les mêmes à la surface.

Toutes ces choses peuvent être favorisées grâce à l'encapsulation et dans ce tutoriel, nous allons tout apprendre à ce sujet. Commençons !

Comment l'encapsulation est-elle réalisée en Python ?

Python prend en charge l'encapsulation par le biais de conventions et de pratiques de programmation plutôt que par des modificateurs d'accès imposés. Vous voyez, le principe fondamental qui sous-tend une grande partie de la conception du code Python est "Nous sommes tous des adultes ici". Nous ne pouvons mettre en œuvre l'encapsulation que comme une simple convention et attendre des autres développeurs Python qu'ils fassent confiance à notre code et le respectent.

Dans d'autres langages OOP tels que Java et C++, l'encapsulation est strictement appliquée avec des modificateurs d'accès tels que public, private ou protected, mais Python n'a pas ces modificateurs.

Ainsi, la plupart, sinon la totalité, des techniques d'encapsulation que je vais vous présenter sont des conventions de Python. Ils peuvent facilement être cassés si vous le décidez. Mais j'espère que vous les respecterez et les suivrez dans vos propres projets de développement.

Modificateurs d'accès en Python

Supposons que nous ayons cette classe simple :

class Tree:
   def __init__(self, height):
       self.height = height

pine = Tree(20)
print(pine.height)
20

Il possède un seul attribut de hauteur que nous pouvons imprimer. Le problème, c'est que nous pouvons aussi le modifier à notre guise :

pine.height = 50
pine.height
50
pine.height = "Grandma"
pine.height
'Grandma'

Alors, comment dire aux utilisateurs qu'il est interdit de changer de hauteur ? Eh bien, nous pourrions en faire un membre protégé en ajoutant un seul trait de soulignement :

class Tree:
   def __init__(self, height):
       self._height = height

pine = Tree(20)
pine._height
20

Les personnes qui connaissent cette convention savent qu'elles ne peuvent accéder qu'à l'attribut et que nous les décourageons fortement de l'utiliser et de le modifier. Mais s'ils le souhaitent, ils peuvent le modifier, oh oui.

Alors, comment éviter cela aussi ? En utilisant une autre convention, transformez l'attribut en un membre privé en ajoutant un double trait de soulignement :

class Tree:
   def __init__(self, height):
       self.__height = height


pine = Tree(20)
pine.__height
AttributeError: 'Tree' object has no attribute '__height'

Désormais, Python lèvera une erreur si quelqu'un tente d'accéder à l'attribut, et a fortiori de le modifier.

Mais avez-vous remarqué ce que nous venons de faire ? Nous cachons aux utilisateurs les seules informations relatives à nos objets. Notre classe est devenue inutile parce qu'elle n'a pas d'attributs publics.

Alors, comment exposer la hauteur des arbres aux utilisateurs tout en contrôlant la manière dont ils y accèdent et les modifient ? Par exemple, nous voulons que la hauteur des arbres se situe dans une fourchette spécifique et ne comporte que des valeurs entières. Comment faire respecter cette règle ?

À ce stade, votre ami qui utilise Java pourrait intervenir et vous suggérer d'utiliser les méthodes getter et setter. Essayons donc d'abord cela :

class Tree:
   def __init__(self, height):
       self.__height = height

   def get_height(self):
       return self.__height

   def set_height(self, new_height):
       if not isinstance(new_height, int):
           raise TypeError("Tree height must be an integer")
       if 0 < new_height <= 40:
           self.__height = new_height
       else:
           raise ValueError("Invalid height for a pine tree")


pine = Tree(20)
pine.get_height()
20

De cette manière, vous créez un attribut privé __height mais vous laissez les utilisateurs y accéder et le modifier de manière contrôlée à l'aide des méthodes get_height et set_height.

pine.set_height(25)

pine.get_height()
25

Avant de définir une nouvelle valeur, set_height s'assure que la nouvelle hauteur se situe dans une certaine fourchette et qu'elle est numérique.

pine.set_height("Password")
TypeError: Tree height must be an integer

Mais ces méthodes semblent excessives pour une opération simple. En outre, il est moche d'écrire un tel code :

# Increase height by 5
pine.set_height(pine.get_height() + 5)

Ne serait-il pas plus beau et plus lisible si nous pouvions écrire ce code :

pine.height += 5

tout en respectant le type de données et l'intervalle corrects pour la hauteur ? La réponse est oui et nous allons apprendre à le faire dans la section suivante.

Utilisation du décorateur @property dans les classes Python

Nous introduisons une nouvelle technique : la création de propriétés pour les attributs :

class Tree:
   def __init__(self, height):
       # First, create a private or protected attribute
       self.__height = height

   @property
   def height(self):
       return self.__height

pine = Tree(17)
pine.height
17

Nous voulons que les utilisateurs accèdent à un attribut caché appelé __height comme s'il s'agissait d'un attribut normal appelé height. Pour ce faire, nous définissons une méthode nommée height qui renvoie self.__height et la décorons avec @property.

Nous pouvons maintenant appeler height et accéder à l'attribut privé :

pine.height
17

Mais le plus beau, c'est que les utilisateurs ne peuvent pas le modifier :

pine.height = 15
AttributeError: can't set attribute 'height'

Nous ajoutons donc une autre méthode appelée height(self, new_height) qui est enveloppée par un décorateur height.setter. Dans cette méthode, nous mettons en œuvre la logique qui impose le type de données et l'intervalle souhaités pour la hauteur :

class Tree:
   def __init__(self, height):
       self.__height = height

   @property
   def height(self):
       return self.__height

   @height.setter
   def height(self, new_height):
       if not isinstance(new_height, int):
           raise TypeError("Tree height must be an integer")
       if 0 < new_height <= 40:
           self.__height = new_height
       else:
           raise ValueError("Invalid height for a pine tree")

Désormais, lorsqu'un utilisateur tente de modifier l'attribut height, @height.setter est appelé, ce qui permet de s'assurer que la valeur transmise est correcte :

pine = Tree(10)

pine.height = 33  # Calling @height.setter
pine.height = 45  # An error is raised
ValueError: Invalid height for a pine tree

Nous pouvons également personnaliser l'accès à l'attribut height par le biais de la notation par points avec @height.getter:

class Tree:
   def __init__(self, height):
       self.__height = height

   @property
   def height(self):
       return self.__height

   @height.getter
   def height(self):
       # You can return a custom version of height
       return f"This tree is {self.__height} meters"


pine = Tree(33)

pine.height
'This tree is 33 meters'

Même si nous avons créé pine avec une hauteur entière, nous pouvons modifier sa valeur avec @height.getter.

Il s'agit là d'exemples de la manière dont nous pouvons promouvoir l'encapsulation dans une classe Python. N'oubliez pas que l'encapsulation reste une convention car nous pouvons toujours briser le membre privé interne __height:

pine._Tree__height = "Gotcha!"

pine.height
'This tree is Gotcha! meters'

Dans les classes Python, tout est public, de même que les méthodes privées. Il ne s'agit pas d'un défaut de conception, mais d'un exemple de l'approche "Nous sommes tous des adultes ici".

Bonnes pratiques pour la mise en œuvre de l'encapsulation

Il existe un certain nombre de bonnes pratiques que vous pouvez suivre pour vous assurer que votre code s'aligne bien sur le code écrit par d'autres OOPistes expérimentés :

  1. Créez des attributs ou des méthodes protégés ou privés s'ils ne sont utilisés que par vous. Les membres protégés ou privés sont exclus de la documentation et signalent aux autres qu'ils peuvent être modifiés par vous, le développeur, sans préavis, ce qui les dissuade de les utiliser.
  2. Il n'est pas toujours nécessaire de créer des propriétés pour chaque attribut de classe. Pour les grandes classes comportant de nombreux attributs, l'écriture des méthodes attr.getter et attr.setter peut devenir un véritable casse-tête.
  3. Envisagez de déclencher un avertissement chaque fois qu'un utilisateur accède à un membre protégé (_).
  4. Utilisez les membres privés avec parcimonie, car ils peuvent rendre le code illisible pour ceux qui ne connaissent pas la convention.
  5. Privilégiez la clarté à l'obscurité. Comme l'encapsulation vise à améliorer la maintenabilité du code et la protection des données, ne cachez pas complètement les détails importants de l'implémentation de votre classe.
  6. Si vous souhaitez créer des propriétés en lecture seule, ne mettez pas en œuvre la méthode @attr.setter. Les utilisateurs pourront accéder à la propriété mais pas la modifier.
  7. Rappelez-vous toujours que l'encapsulation est une convention, et non un aspect obligatoire de la syntaxe Python.
  8. Pour les classes simples, envisagez d'utiliser des classes de données qui vous permettent d'encapsuler la classe avec une seule ligne de code. En revanche, les classes de données sont destinées à des classes plus simples dont les attributs et les méthodes sont prévisibles. Consultez ce tutoriel sur les classes de données pour en savoir plus.

Conclusion et autres ressources

Dans ce tutoriel, nous avons appris l'un des piliers fondamentaux de la programmation orientée objet en Python : l'encapsulation.

L'encapsulation vous permet de définir un accès contrôlé aux données stockées dans les objets de votre classe. Cela vous permet d'écrire un code propre, lisible et efficace et d'éviter toute modification ou suppression accidentelle des données de votre classe.

Voici d'autres ressources qui vous permettront d'approfondir vos connaissances en matière de POO :


Photo of Bex Tuychiev
Author
Bex Tuychiev
LinkedIn

Je suis un créateur de contenu en science des données avec plus de 2 ans d'expérience et l'un des plus grands followings sur Medium. J'aime écrire des articles détaillés sur l'IA et la ML dans un style un peu sarcastıc, car il faut bien faire quelque chose pour les rendre un peu moins ennuyeux. J'ai produit plus de 130 articles et un cours DataCamp, et un autre est en cours d'élaboration. Mon contenu a été vu par plus de 5 millions de personnes, dont 20 000 sont devenues des adeptes sur Medium et LinkedIn. 

Sujets

Poursuivez votre voyage en Python dès aujourd'hui !

Certification disponible

cours

Programmation orientée objet en Python

4 hr
80.1K
Plongez dans cette expérience et apprenez à créer des classes et à tirer parti de l'héritage et du polymorphisme pour réutiliser et optimiser le code.
Afficher les détailsRight Arrow
Commencer Le Cours
Voir plusRight Arrow